アルファベットによるforループ


12

これはOSXで完全に動作します

#!/bin/bash
chars=( {a..z} )
n=3
for ((i=0; i<n; i++))
do
  echo "${chars[i]}"
done

しかし、Ubuntuで実行すると、次のエラーが発生します。

ForLoopAlphabetTest.sh: 2: ForLoopAlphabetTest.sh: Syntax error: "(" unexpected

問題を解決できないようです。助言がありますか?


2
これはUbuntuで機能します。
Pilot6

16.04 bash 4.3でスクリプトとして動作させることはできません。しかし、それをターミナルにコピーしてコピーしても機能します。
デンスキ

回答:


25

おそらく、スクリプトを次のように実行しています。

sh ForLoopAlphabetTest.sh

Ubuntuでは、shにシンボリックリンクされていますdashます; dash配列の概念がありません、あなたは構文エラーを取得しています(

スクリプトはで完全に動作するbashため、次のように実行すると問題ありませんbash引数。

bash ForLoopAlphabetTest.sh

これでbash、スクリプトにシェバンができたので、スクリプトを実行可能にすることができます(chmod u+x ForLoopAlphabetTest.sh)にして、次のように。

/path/to/ForLoopAlphabetTest.sh

またはスクリプトのディレクトリから:

./ForLoopAlphabetTest.sh

また、スクリプトにはブレース展開{a..z}とCスタイルのfor構造が含まれていることに注意してください。for (( ... ))これらは、dash; でもサポートされていません。したがって、移植性が目標の場合は、POSIX sh構文のみを検討する必要があります。


ありがとうございました。配列の概念のダッシュの欠如を回避する方法はありますか?
デンスキ

3
@denski /bin/shUnixライクなオペレーティングシステムで実行できる移植可能なスクリプトを作成する場合、配列を使用することはできません。Bash(および他のいくつかのシェル)は非常に便利であり、常により移植性の高いコードに簡単に置き換えることができないため、それらを追加しました。ただし、特にスクリプトの場合は、bash固有の機能を使用せずに問題なく実行できます。その方法に興味がありますか?
エリアケイガン

読書を提案している場合、それは有益です。ありがとう。
デンスキ

1
@denski いくつかのリンクと例を含む回答投稿しました。ここでの以前のコメントで、配列とCスタイルのforループを使用したことは言及しましたが、ブレース展開の使用については言及しませんでした。私の答えは、3つすべてなしで行う方法をカバーしています。この答え(つまり、私のものではなく、ヘマイルのもの)が問題の主な解決策であることに注意してください。私は、bash固有の機能に依存できなかった場合に、スクリプトをどのように書き換えるかに焦点を当てています。
エリアケイガン

@heemayl記録のために、私はスクリプトを実行しているというあなたの仮定が正しいことを付け加えたいとsh
思いました-denski

10

スクリプトは、すべてのBourneスタイルのシェルでは提供されないBashシェルの3つの機能を使用します。以下のようheemaylは言う、あなたは単にでそのスクリプトを実行することができますbash代わりにsh。あなたのhashbangの一番上(のライン#!/bin/bash)を指定しbashていますが場合にのみ有効であり、実行として、スクリプトをheemaylを説明。スクリプトの名前をに渡すと、が自動的に呼び出されるのshshはなくbash、単にスクリプトが実行されます。これは、スクリプトが実際に実行されると、hashbang行は効果がないためです。

完全にポータブルな記述が必要な場合、他の選択肢Bashの機能に依存しないスクリプトスクリプトを変更して、スクリプトを使用せずに機能するようにすることもできます。使用するBash機能は次のとおりです。

  • 配列。これが最初であったため、Dashシェルでスクリプトを実行しようとしたときにエラーが発生しました。に( {a..z} )割り当てた括弧で囲まれた式charsは、配列を作成し、${chars[i]}し、ループに表示されるが、その中にインデックスを付けます。
  • ブレースの拡張。Bashおよび他の多くのシェルでは、{a..z}は、に展開されa b c d e f g h i j k l m n o p q r s t u v w x y zます。ただし、これはBourneスタイルのシェルの普遍的な(または標準化された)機能ではなく、Dashはサポートしていません。
  • Cスタイルの代替forループ構文それ自体がBashに固有ではない算術展開に基づいていますがPOSIXに準拠していない非常に古いシェルもいくつかありませんが)、Cスタイルのforループ Bash主義であり、他のシェル。

Bashは、特にUbuntuのようなGNU / Linuxシステムで広く利用可能です。また、(ご存知のように)macOSや他の多くのシステムでも利用可能です。Bash固有の機能をどれだけ使用しているかを考慮すると、単にスクリプトを実行するときにそれらを使用し、Bash(または使用している機能をサポートする他のシェル)を使用していることを確認するだけです。

ただし、必要に応じて、移植可能な構成要素に置き換えることができます。配列とCスタイルのforループは簡単に交換できます。中括弧を展開せずに(およびスクリプト内でハードコーディングせずに)一連の文字を生成することは、少し注意が必要な部分です。


まず、小文字のラテン文字をすべて印刷するスクリプトを次に示します。

#!/bin/sh

for i in $(seq 97 122); do
    printf "\\$(printf %o $i)\n"
done
  • このseqコマンドは、数値シーケンスを生成します。コマンド置換を$( )実行するため、の出力に置き換えられます。これらは、文字コードのための経由。$(seq 97 122)seq 97 122az
  • 強力なprintfコマンドは、文字コードを文字に変換できます(たとえば、printf '\141'printsのa後に改行が続きます)が、コードは8進数でなければならseq出力は10進数のみなければなりません。したがってprintf、2回使用しました。内側はprintf %o $i10進数(で提供されるseq)を8 進数に変換し、外側のコマンドに置き換えられprintfます。(16進数を使用することも可能ですが、単純でなく、移植性が低いようです。)
  • printf\8進数が続くコードをそのコードの文字\nとして、および改行として解釈します。ただし、シェル\はエスケープ文字としても使用します。A \の前に$防ぐことができます$から発生する拡張原因(この場合は、コマンド置換を)、私は別でそれを逃れてきたので、私は、それを防ぐためにしたくありません\。それが理由です\\\前の2つ目nはエスケープする必要はありません。なぜなら\$\n二重引用符で囲まれた文字列ではシェル特別な意味がないからです。
  • シェルプログラミングでの二重引用符とバックスラッシュの使用方法の詳細については、国際標準の引用に関するセクションを参照してください。参照してください。3.1.2引用バッシュ・リファレンス・マニュアル、特に、3.1.2.1エスケープ文字3.1.2.3二重引用符。(セクション全体がコンテキストにあります。)シングルクォート(')もシェルクォート構文の重要な部分であることに注意してください。私はたまたまそのスクリプトでそれらを使用していません。

これは、ほとんどの Unixライクシステムに移植可能であり、使用するBourneスタイルのシェルに依存しません。ただし、いくつかのUnixライクなシステムはseqデフォルトではインストールされていません(jotデフォルトではほとんどのGNU / Linuxシステムにインストールされていない代わりに使用する傾向があります)。expr必要に応じて、ループを使用するか算術置換を使用して、移植性をさらに高めることができます。

#!/bin/sh

i=97
while [ $i -le 122 ]; do
    printf "\\$(printf %o $i)\n"
    i=$((i + 1))
done

それは使用whileして-loopを指示するときのみ、ループを継続する範囲です。[$i


スクリプトはアルファベット全体を印刷するのではなく、変数nを定義して最初の$n小文字を印刷します。Bash固有の機能に依存せず、Dashで動作するが、必要なスクリプトのバージョンはseq次のとおりです。

#!/bin/sh

n=3 start=97
for i in $(seq $start $((start + n - 1))); do
    printf "\\$(printf %o $i)\n"
done

の値を調整する nスクリプトのように印刷される文字数が変わります。

必要のないバージョンはseq次のとおりです。

#!/bin/sh

n=3 i=97 stop=$((i + n))
while [ $i -lt $stop ]; do
    printf "\\$(printf %o $i)\n"
    i=$((i + 1))
done

そこに、印刷されるべき最後の文字の文字コードより$stop1つ高いので、コマンド-ltでは-le(より小さいか)ではなく(より小さい)を使用します[。(を作成stop=$((i + n - 1))して使用することもできたでしょう[ $i -le $stop ])。


1
これは、教育に対するあなたよりも驚くほど詳細な答えです。私は非常に初心者なので、スクリプトを書く私の方法は、インターネット上で見つかった作業要素を、それが機能するまでまとめることです。スクリプトを使用して作成していることを1)より良く、2)より簡単に行う方法があることは間違いありませんが、上記はそれを支援するのに大いに役立ちます。
デンスキ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.