1941

範囲が変数で指定されている場合、Bash で数値の範囲を反復処理するにはどうすればよいですか?

私はこれを行うことができることを知っています(Bashドキュメントでは「シーケンス式」と呼ばれます):

 for i in {1..5}; do echo $i; done

これにより、次のことが得られます。

1
2
3
4
5

しかし、範囲のエンドポイントのいずれかを変数に置き換えるにはどうすればよいでしょうか? これは機能しません:

END=5
for i in {1..$END}; do echo $i; done

どちらが印刷されますか:

{1..5}

4

20 に答える 20

2201
for i in $(seq 1 $END); do echo $i; done

seq編集:実際に覚えているので、他の方法よりも好きです;)

于 2008-10-04T01:41:55.673 に答える
628

このseq方法は最も単純ですが、Bash には算術評価が組み込まれています。

END=5
for ((i=1;i<=END;i++)); do
    echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines

for ((expr1;expr2;expr3));コンストラクトは C や類似の言語と同じようにfor (expr1;expr2;expr3)機能し、他の((expr))場合と同様に、Bash はそれらを算術として扱います。

于 2008-10-04T21:43:47.913 に答える
210

討論

seqJiaaroが提案したように、使用は問題ありません。Pax Diablo は、サブプロセスの呼び出しを回避するために Bash ループを提案しました。これには、$END が大きすぎる場合によりメモリに優しくなるという追加の利点があります。Zathrus は、ループの実装に典型的なバグを発見し、sinceiはテキスト変数であるため、数値との間の連続的な変換が実行され、関連するスローダウンが発生することをほのめかしました。

整数演算

これは Bash ループの改良版です。

typeset -i i END
let END=5 i=1
while ((i<=END)); do
    echo $i
    …
    let i++
done

必要なものが のみである場合、 とecho書くことができますecho $((i++))

ephemientは私に何かを教えてくれました: Bash はfor ((expr;expr;expr))コンストラクトを許可します。私は Bash のマニュアル ページ全体を読んだことがないので (Korn シェル ( ksh) のマニュアル ページで読んだことがありますが、それはずっと前のことです)、それを見逃していました。

そう、

typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done

おそらく「最速」ではありませんが、最もメモリ効率の高い方法のようです ( の出力を消費するためにメモリを割り当てる必要はありませんseq。これは END が非常に大きい場合に問題になる可能性があります)。

最初の質問

eschercycle は、{ a .. b } Bash 表記がリテラルでのみ機能することを指摘しました。true、Bash マニュアルに従って。fork()なしで単一の(内部)でこの障害を克服できます( fork + exec が必要な別のイメージであるexec()を呼び出す場合と同様):seq

for i in $(eval echo "{1..$END}"); do

evalとはどちらechoも Bash 組み込みfork()ですが、コマンド置換 ($(…)コンストラクト) には a が必要です。

于 2008-10-04T02:38:48.710 に答える
113

元の式が機能しなかった理由は次のとおりです。

man bashから:

ブレース展開は他の展開よりも前に実行され、他の展開に特有の文字は結果に保持されます。それは厳密にテキストです。Bash は、展開のコンテキストまたは中括弧内のテキストに構文解釈を適用しません。

したがって、ブレース展開は、パラメーター展開の前に、純粋にテキストのマクロ操作として早期に行われるものです。

シェルは、マクロ プロセッサとより正式なプログラミング言語との間の高度に最適化されたハイブリッドです。典型的なユースケースを最適化するために、言語はかなり複雑になり、いくつかの制限が受け入れられます。

おすすめ

Posix 1の機能に固執することをお勧めします。これはfor i in <list>; do、リストが既知の場合は を使用することを意味し、そうでない場合は、whileまたはseqを次のように使用します。

#!/bin/sh

limit=4

i=1; while [ $i -le $limit ]; do
  echo $i
  i=$(($i + 1))
done
# Or -----------------------
for i in $(seq 1 $limit); do
  echo $i
done


1. Bash は優れたシェルであり、対話的に使用していますが、スクリプトに bash-ism を入れていません。スクリプトには、より高速なシェル、より安全なシェル、より組み込みスタイルのシェルが必要になる場合があります。/bin/sh としてインストールされているもので実行する必要がある場合があり、通常のプロ標準の引数がすべてあります。シェルショック、別名バッシュドアを覚えていますか?

于 2011-04-19T22:32:44.227 に答える
92

POSIX のやり方

移植性に関心がある場合は、POSIX 標準の例を使用してください。

i=2
end=5
while [ $i -le $end ]; do
    echo $i
    i=$(($i+1))
done

出力:

2
3
4
5

POSIXでないもの:

  • (( ))POSIX自体で言及されているように、ドルは一般的な拡張機能ですが、ドルはありません。
  • [[. [ここで十分です。参照: Bash の単一角括弧と二重角括弧の違いは何ですか?
  • for ((;;))
  • seq(GNU Coreutils)
  • {start..end}、およびBash manualで言及されているように、変数では機能しません。
  • let i=i+1: POSIX 7 2. シェル コマンド言語に単語が含まれておらず、4.3.42letで失敗するbash --posix
  • ドルi=$i+1が必要かもしれませんが、よくわかりません。POSIX 7 2.6.4 算術展開は次のように述べています。

    シェル変数 x が有効な整数定数を形成する値を含み、必要に応じて先頭のプラス記号またはマイナス記号を含む場合、算術展開 "$((x))" および "$(($x))" は同じ値を返します。価値。

    しかし、文字通りそれを読んでも、 が変数ではない$((x+1))ので展開することを意味するものではありません。x+1

于 2015-07-12T07:54:44.353 に答える
36

間接的な別のレイヤー:

for i in $(eval echo {1..$END}); do
    ∶
于 2011-03-14T19:51:36.677 に答える
35

使用できます

for i in $(seq $END); do echo $i; done
于 2008-10-04T01:41:23.753 に答える
21

BSD / OS X を使用している場合は、seq の代わりに jot を使用できます。

for i in $(jot $END); do echo $i; done
于 2011-03-15T23:29:16.203 に答える
18

これは次の場合に正常に機能しbashます。

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done
于 2008-10-04T01:42:39.883 に答える
8

この質問が に関するものであることは知っていますがbash、記録のために-ksh93よりスマートで、期待どおりに実装しています:

$ ksh -c 'i=5; for x in {1..$i}; do echo "$x"; done'
1
2
3
4
5
$ ksh -c 'echo $KSH_VERSION'
Version JM 93u+ 2012-02-29

$ bash -c 'i=5; for x in {1..$i}; do echo "$x"; done'
{1..5}
于 2013-09-19T12:33:02.677 に答える
8

これは別の方法です:

end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
于 2015-07-12T12:36:51.050 に答える
8

ブレース式の構文にできるだけ近づけたい場合はrange、bash-tricks の関数をrange.bash試してください。

たとえば、次のすべては とまったく同じことを行いますecho {1..10}

source range.bash
one=1
ten=10

range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}

可能な限り「落とし穴」を少なくして、ネイティブの bash 構文をサポートしようとします。変数がサポートされるだけでなく、無効な範囲が文字列として提供されるという望ましくない動作 (例: for i in {1..a}; do echo $i; done) も防止されます。

他の回答はほとんどの場合に機能しますが、次の欠点の少なくとも 1 つがあります。

  • それらの多くはサブシェルを使用していますが、これはパフォーマンスに悪影響を与える可能性があり、一部のシステムでは使用できない場合があります。
  • それらの多くは外部プログラムに依存しています。seq使用するためにインストールする必要があるバイナリであり、bash によってロードする必要があり、この場合に機能するためには、期待するプログラムを含める必要があります。ユビキタスであろうとなかろうと、それは単なる Bash 言語自体よりもはるかに信頼できるものです。
  • @ephemient のようなネイティブの Bash 機能のみを使用するソリューションは、{a..z};のようなアルファベットの範囲では機能しません。ブレース拡張します。ただし、質問は数値の範囲に関するものだったので、これは疑問です。
  • それらのほとんどは、{1..10}ブレース拡張範囲構文と視覚的に似ていないため、両方を使用するプログラムは少し読みにくい場合があります。
  • @bobbogo の回答では、おなじみの構文をいくつか使用していますが、$END変数が範囲の反対側の有効な範囲「ブックエンド」でない場合、予期しないことを行います。たとえばEND=a、エラーは発生せず、逐語的な値{1..a}がエコーされます。これは、Bash のデフォルトの動作でもあります。多くの場合、予期しない動作です。

免責事項: 私はリンクされたコードの作成者です。

于 2017-07-27T13:30:48.167 に答える
7

これらはすべて素晴らしいですが、 seq はおそらく非推奨であり、ほとんどは数値範囲でのみ機能します。

for ループを二重引用符で囲むと、文字列をエコーするときに開始変数と終了変数が逆参照され、実行のために文字列を BASH に直接送り返すことができます。$iサブシェルに送信される前に評価されないように、\ でエスケープする必要があります。

RANGE_START=a
RANGE_END=z
echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash

この出力は、変数に割り当てることもできます。

VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash`

これが生成する唯一の「オーバーヘッド」は bash の 2 番目のインスタンスである必要があるため、集中的な操作に適しているはずです。

于 2011-08-16T21:18:30.490 に答える