Bashの変数で定義された数値の範囲を反復するにはどうすればよいですか?


1544

範囲が変数で指定されている場合、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}


26
こんにちは、私がここで読んだ情報とヒントはすべて本当に役に立ちます。seqの使用は避けるのが最善だと思います。その理由は、一部のスクリプトは移植可能である必要があり、一部のコマンドが存在しない可能性があるさまざまなUNIXシステムで実行する必要があるためです。例をあげると、FreeBSDシステムではデフォルトでseqは存在しません。


9
どのバージョンのBashなのか正確には覚えていませんが、このコマンドは末尾のゼロもサポートしています。これは時々本当に役に立ちます。コマンドfor i in {01..10}; do echo $i; doneはのような数字を与えます01, 02, 03, ..., 10
topr

1
私のように配列のインデックスの範囲を繰り返し処理したい場合、bashの方法は次のようになりますmyarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done(感嘆符に注意してください)。元の質問よりも具体的ですが、役立つ場合があります。bashパラメータの展開を
PlasmaBinturong 2018

1
ブレース展開は{jpg,png,gif}、ここでは直接触れられていないような表現にも使用されますが、答えは同じです。変数によるブレース展開を参照してください[duplicate]これはこれの複製としてマークされています。
tripleee

回答:


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

編集:私seqは実際にそれを覚えることができるので、私は他の方法よりも好みます;)


36
seqは、通常、速度を低下させる外部コマンドの実行を伴います。これは問題ではないかもしれませんが、大量のデータを処理するスクリプトを作成している場合は重要になります。
paxdiablo 2008年

37
ワンライナーには問題ありません。Paxのソリューションも問題ありませんが、パフォーマンスが本当に懸念される場合は、シェルスクリプトを使用しません。
eschercycle 2008年

17
seqは、数値を生成するために1回だけ呼び出されます。exec()を実行しても、このループが別のタイトなループ内にある場合を除き、重要ではありません。
ハビエル

29
外部コマンドは実際には関連性がありません。外部コマンドの実行のオーバーヘッドが心配な場合は、シェルスクリプトをまったく使用する必要はありませんが、一般的にUNIXではオーバーヘッドはわずかです。ただし、ENDが高い場合、メモリ使用量の問題があります。
Mark Ba​​ker、

18
seq $ENDデフォルトでは1から始まるので、これで十分であることに注意してくださいman seq
fedorqui 'SO stop harming' 2014

474

この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));構築物は、同じように動作for (expr1;expr2;expr3)Cと似た言語であり、他の同じよう((expr))な場合、bashは算術として扱います。


67
この方法により、大きなリストのメモリオーバーヘッド、およびへの依存を回避できseqます。これを使って!
bobbogo 2011年

3
@MarinSagovacこれ機能し、構文エラーはありません。あなたのシェルはバッシュですか?
gniourf_gniourf 2015

3
@MarinSagovac #!/bin/bashスクリプトの最初の行を必ず作成してください。wiki.ubuntu.com/…– Melebius 2017
13:22

7
それについての非常に短い質問:なぜ((i = 1; i <= END; i ++))AND NOT((i = 1; i <= $ END; i ++)); なぜENDの前に$がないのですか?
Baedsch 2018

5
@Baedsch:同じ理由で、iは$ iとして使用されません。算術評価のためのbashのマニュアルページの状態:「式内では、パラメーター展開構文を使用せずに、シェル変数を名前で参照することもできます。」
user3188140

193

討論

seqジアアロが示唆したように、使用は問題ありません。Pax Diabloは、サブプロセスの呼び出しを回避するためにBashループを提案しました。さらに、$ ENDが大きすぎる場合により多くのメモリフレンドリーになるという利点もあります。Zathrusは、ループの実装における典型的なバグを発見しi、テキスト変数であるため、前後の数値への連続的な変換は、関連するスローダウンとともに実行されることを示唆しました。

整数演算

これは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表記はリテラルでのみ機能することに注意しました。Bashのマニュアルに従ってtrue。1つ(内部)fork()なしでこの障害を克服することができますexec()(を呼び出す場合のようにseq、別のイメージであるため、fork + execが必要です)。

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

evalechoはどちらもBashビルトインfork()ですが、コマンド置換($(…)構成)にはa が必要です。


1
"$"で始まるため、コマンドライン引数を使用できないというCスタイルループの唯一の欠点。
空手犬2012年

3
@karatedog:for ((i=$1;i<=$2;++i)); do echo $i; doneスクリプトではbash v.4.1.9で問題なく動作するため、コマンドライン引数に問題はありません。他の意味ですか?
tzot

evalソリューションは組み込みのCライクよりも高速であるようです:$ time for((i = 1; i <= 100000; ++ i)); 行う :; $ 0で実際の0m21.220sユーザー0m19.763s sys 0m1.203s $時間を$(eval echo "{1..100000}"); 行う :; 完了。実際0m13.881sユーザー0m13.536s sys 0m0.152s
Marcin Zaluski

3
はい、しかしevalは悪です... @MarcinZaluski time for i in $(seq 100000); do :; doneはもっと速いです!
F.ハウリ2013

私のマシンではevalバージョンが最も速いため、パフォーマンスはプラットフォーム固有でなければなりません。
Andrew Prock 14

103

これが、元の表現が機能しなかった理由です。

man bashから:

ブレース展開は他の展開の前に実行され、他の展開に固有の文字は結果に保持されます。厳密にテキストです。bashは、ブレース間の展開またはテキストのコンテキストに構文解釈を適用しません。

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

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

勧告

私はPosix 1の機能を使い続けることをお勧めします。つまりfor i in <list>; do、リストが既知の場合はを使用します。それ以外の場合は、次のようにwhileorを使用します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-ismsをスクリプトに含めません。スクリプトには、より高速なシェル、より安全なシェル、より組み込みスタイルのシェルが必要になる場合があります。それらは/ bin / shとしてインストールされたもので実行する必要があるかもしれません、そしてそれからすべての通常のプロ規格の引数があります。覚えている2014年シェルショック脆弱性を、別名bashdoor?


13
私には力はありませんが、私はこれをリストのかなり上に移動します。何よりもbashおへそを見て、Cスタイルのforループと算術評価の直後です。
mateor 2013年

2
含意は、ブレース展開はseq大きな範囲の場合と比較して多くのメモリを節約しないことです。たとえば、echo {1..1000000} | wcエコーが1行、100万ワード、6,888,896バイトを生成することを明らかにします。しようとseq 1 1000000 | wc利回り万行、万語、および6888896のバイトをしてによって測定されるように、7倍以上速くもあるtimeコマンド。
ジョージ

注:私はwhile以前に私の回答でPOSIX メソッドについて言及しました:stackoverflow.com/a/31365662/895245しかし、あなたが同意してうれしいです:-)
Ciro Santilli冠状病毒审查六四事件法轮機能

以下のパフォーマンス比較の回答にこの回答を含めました。stackoverflow.com/a/54770805/117471(これは、私が残したものを追跡するための自分へのメモです。)
Bruno Bronosky

@mateor Cスタイルのforループと算術評価は同じソリューションだと思いました。何か不足していますか?
Oscar Zhang

73

POSIXの方法

移植性を重視する場合は、POSIX標準の例を使用してください。

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

出力:

2
3
4
5

POSIX ではないもの:


非常に珍しいこの回答に4つの賛成票がありました。これがリンク集約Webサイトに投稿された場​​合は、リンクをお願いします。
Ciro Santilli冠状病毒审查六四事件法轮功

引用符はx式全体ではなくを指します。$((x + 1))大丈夫です
chepner

移植性がなく、GNU seq(BSD seqではシーケンス終了文字列をで設定できる)とは異なりますが-t、FreeBSDとNetBSDもseqそれぞれ9.0と3.0以降に対応しています。
エイドリアン・ギュンター

@CiroSantilli @chepner $((x+1))$((x + 1))パーサはトークン化するときと、まったく同じを解析しx+1、それは3つのトークンに分割されます:x+、と1xは有効な数値トークンではありませんが、有効な変数名トークンですが、x+そうではないため、分割されます。+は有効な算術演算子トークンです+1が、そうではないため、トークンはそこで再び分割されます。などなど。
エイドリアン・ギュンター

以下のパフォーマンス比較の回答にこの回答を含めました。stackoverflow.com/a/54770805/117471(これは、私が残したものを追跡するための自分へのメモです。)
Bruno Bronosky


28

使用できます

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

seqは、通常、速度を低下させる外部コマンドの実行を伴います。
paxdiablo 2008年

9
反復ごとに外部コマンドを1回だけ実行する必要はありません。1つの外部コマンドを起動する時間が問題になる場合は、間違った言語を使用しています。
Mark Ba​​ker、

1
それで、これが問題になるのはネストだけですか?パフォーマンスに違いがあるのか​​、不明な技術的な副作用があるのか​​と思いました。
Sqeaky、2012年

:ここで答えwhichtis別の質問だ@Squeaky stackoverflow.com/questions/4708549/...
tripleee

以下のパフォーマンス比較の回答にこの回答を含めました。stackoverflow.com/a/54770805/117471(これは、私が残したものを追跡するための自分へのメモです。)
Bruno Bronosky

21

あなたがこれを好きかもしれないよりもそれがプレフィックスを必要とするならば

 for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done

それは降ります

07
08
09
10
11
12

4
printf "%02d\n" $iより簡単ではないでしょうprintf "%2.0d\n" $i |sed "s/ /0/"か?
zb226

19

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

for i in $(jot $END); do echo $i; done

17

これは正常に動作しbashます:

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

6
echo $((i++))機能し、それを1行に結合します。
Bruno Bronosky 2014

1
これには不要なbash拡張機能があります。POSIXバージョン:stackoverflow.com/a/31365662/895245
Ciro Santilli冠状病毒审查六四事件法轮機能

1
@Ciro、質問には特にbashが記載されており、bashタグがあるので、bashの「拡張機能」は大丈夫だと思うでしょう:-)
paxdiablo

@paxdiabloそれが正しくないことを意味しているわけではありませんが、可能な場合は移植可能にしないでください;-)
Ciro Santilli冠状病毒审查六四事件法轮機能

ではbashwhile [[ i++ -le "$END" ]]; doテストで(ポスト)インクリメントを実行するだけで済みます
アーロンマクダイド

14

ここでいくつかのアイデアを組み合わせ、パフォーマンスを測定しました。

TL; DRのポイント:

  1. seqそして{..}本当に速い
  2. forそしてwhileループは遅いです
  3. $( ) 遅い
  4. for (( ; ; )) ループが遅い
  5. $(( )) さらに遅い
  6. メモリ内のN個の数値(seqまたは{..})を心配するのはばかげています(少なくとも100万まで)。

これらは結論ではありません。結論を出すには、これらのそれぞれの背後にあるCコードを調べる必要があります。これは、コードをループするためにこれらの各メカニズムをどのように使用するかについての詳細です。ほとんどの単一操作は、ほとんどの場合問題にならない速度と同じくらい十分に近いです。しかし、のようなメカニズムfor (( i=1; i<=1000000; i++ ))は、視覚的にわかるように多くの操作です。また、から得られるよりも、ループごとの操作がはるかに多くなりますfor i in $(seq 1 1000000)。そして、それはあなたには明らかではないかもしれません。そのため、このようなテストを行うことは価値があります。

デモ

# show that seq is fast
$ time (seq 1 1000000 | wc)
 1000000 1000000 6888894

real    0m0.227s
user    0m0.239s
sys     0m0.008s

# show that {..} is fast
$ time (echo {1..1000000} | wc)
       1 1000000 6888896

real    0m1.778s
user    0m1.735s
sys     0m0.072s

# Show that for loops (even with a : noop) are slow
$ time (for i in {1..1000000} ; do :; done | wc)
       0       0       0

real    0m3.642s
user    0m3.582s
sys 0m0.057s

# show that echo is slow
$ time (for i in {1..1000000} ; do echo $i; done | wc)
 1000000 1000000 6888896

real    0m7.480s
user    0m6.803s
sys     0m2.580s

$ time (for i in $(seq 1 1000000) ; do echo $i; done | wc)
 1000000 1000000 6888894

real    0m7.029s
user    0m6.335s
sys     0m2.666s

# show that C-style for loops are slower
$ time (for (( i=1; i<=1000000; i++ )) ; do echo $i; done | wc)
 1000000 1000000 6888896

real    0m12.391s
user    0m11.069s
sys     0m3.437s

# show that arithmetic expansion is even slower
$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; i=$(($i+1)); done | wc)
 1000000 1000000 6888896

real    0m19.696s
user    0m18.017s
sys     0m3.806s

$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; ((i=i+1)); done | wc)
 1000000 1000000 6888896

real    0m18.629s
user    0m16.843s
sys     0m3.936s

$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $((i++)); done | wc)
 1000000 1000000 6888896

real    0m17.012s
user    0m15.319s
sys     0m3.906s

# even a noop is slow
$ time (i=1; e=1000000; while [ $((i++)) -le $e ]; do :; done | wc)
       0       0       0

real    0m12.679s
user    0m11.658s
sys 0m1.004s

1
いいね!しかし、あなたの要約に同意しないでください。に見えるの$(seq)はとほぼ同じ速度{a..b}です。また、各操作にはほぼ同じ時間がかかるため、ループの各反復に約4μsを追加します。ここでの操作とは、体内のエコー、算術比較、増分などです。これは驚くべきことですか?ループの道具がその仕事をするのにどれくらい時間がかかるかを気にする人は誰ですか?ランタイムはループの内容によって支配される可能性が高いです。
bobbogo

@bobbogoあなたは正しい、それは本当に操作数についてです。私はこれを反映するために私の答えを更新しました。実際に行う多くの呼び出しは、予想よりも多くの操作を実行します。実行した約50のテストのリストからこれを絞り込みました。私の研究はこの群衆にとってもオタクすぎると思っていました。いつものように、私はあなたのコーディングの取り組みに次のような優先順位を付けることを提案します。読みやすくする。より速くする; ポータブルにする。多くの場合、#1は#3を引き起こします。必要になるまで#4で時間を無駄にしないでください。
ブルーノブロノスキー

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}

8

これは別の方法です:

end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done

1
これには、別のシェルを生成するオーバーヘッドがあります。
codeforester 2017年

1
実際には、1つで十分なときに2つのシェルを生成するため、これはさらにひどいです。
ブルーノブロノスキー

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のデフォルトの動作でもあります。予期しないことがよくあります。

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



6

これらはすべて素晴らしいですが、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番目のインスタンスである必要があるため、集中的な操作に適しています。


5

シェルコマンドを実行していて、(私と同じように)パイプライン処理にフェチがある場合、これは適切です。

seq 1 $END | xargs -I {} echo {}


3

これには多くの方法がありますが、私が好む方法を以下に示します

使用する seq

あらすじ man seq

$ seq [-w] [-f format] [-s string] [-t string] [first [incr]] last

構文

完全なコマンド
seq first incr last

  • 最初はシーケンスの開始番号です[オプション、デフォルトでは1]
  • incrはインクリメントです[オプションで、デフォルトでは1]
  • lastはシーケンスの最後の番号です

例:

$ seq 1 2 10
1 3 5 7 9

最初と最後のみ:

$ seq 1 5
1 2 3 4 5

最後のみ:

$ seq 5
1 2 3 4 5

使用する {first..last..incr}

ここで最初と最後は必須で、増分はオプションです

最初と最後だけを使用する

$ echo {1..5}
1 2 3 4 5

incrの使用

$ echo {1..10..2}
1 3 5 7 9

以下のようなキャラクターでも使用できます

$ echo {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

3

' seq'または ' eval' jotまたは算術展開形式を使用したくない場合。for ((i=1;i<=END;i++))、または他のループ。while、そして「printf」と「echo」だけに満足したくない場合は、この簡単な回避策が予算に合う可能性があります。

a=1; b=5; d='for i in {'$a'..'$b'}; do echo -n "$i"; done;' echo "$d" | bash

PS:私のbashにはseqとにかく' 'コマンドがありません。

Mac OSX 10.6.8、Bash 3.2.48でテスト済み


0

これはBashとKornで機能し、数値が高いものから低いものへと移行することもあります。おそらく最速でもきれいでもありませんが、十分に機能します。ネガも扱います。

function num_range {
   # Return a range of whole numbers from beginning value to ending value.
   # >>> num_range start end
   # start: Whole number to start with.
   # end: Whole number to end with.
   typeset s e v
   s=${1}
   e=${2}
   if (( ${e} >= ${s} )); then
      v=${s}
      while (( ${v} <= ${e} )); do
         echo ${v}
         ((v=v+1))
      done
   elif (( ${e} < ${s} )); then
      v=${s}
      while (( ${v} >= ${e} )); do
         echo ${v}
         ((v=v-1))
      done
   fi
}

function test_num_range {
   num_range 1 3 | egrep "1|2|3" | assert_lc 3
   num_range 1 3 | head -1 | assert_eq 1
   num_range -1 1 | head -1 | assert_eq "-1"
   num_range 3 1 | egrep "1|2|3" | assert_lc 3
   num_range 3 1 | head -1 | assert_eq 3
   num_range 1 -1 | tail -1 | assert_eq "-1"
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.