範囲が変数で指定されている場合、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}
for i in $(seq 1 $END); do echo $i; done
編集:私は実際にそれを覚えていることができるので私は他の方法よりseq
を好みます;)
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はそれらを算術として扱います。
Jiaaroが示唆しているように、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の全manページを読んだことがないので(Korn Shell(ksh
)のmanページを使ったのと同じように、それは昔のことでした)、私はそれを逃しました。
そう、
typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done
おそらく最も速い方法ではありませんが、最もメモリ効率の良い方法と思われます(seq
の出力を消費するためにメモリを割り当てる必要はありません)。
eschercycleは、{ a .. b } Bash表記はリテラルでのみ機能することに注意しました。したがって、Bashのマニュアルに従ってください。 fork()
なしで単一の(内部の)exec()
を使うことでこの障害を克服することができます(別のイメージであるseq
を呼び出す場合のように、fork + execが必要です)。
for i in $(eval echo "{1..$END}"); do
eval
とecho
はどちらもBashの組み込み関数ですが、コマンド置換にはfork()
が必要です($(…)
構文)。
これが、元の表現が機能しなかった理由です。
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
POSIXの方法
移植性を重視する場合は、 POSIX標準の例 を使用します。
i=2
end=5
while [ $i -le $end ]; do
echo $i
i=$(($i+1))
done
出力:
2
3
4
5
POSIXであるnotもの:
(( ))
ドルなし。ただし、一般的な拡張子です POSIX自体で述べられているように 。[[
。 [
で十分です。以下も参照してください。 Bashの角かっこ1つと2つとの違いは何ですか?for ((;;))
seq
(GNU Coreutils){start..end}
、およびそれは前述のように変数を処理できません Bashマニュアルによる 。let i=i+1
: POSIX 7 2. Shell Command Language にはlet
という単語が含まれておらず、bash --posix
4.3.42で失敗しますi=$i+1
のドルが必要になる場合がありますが、わかりません。 POSIX 7 2.6.4算術展開 言います:
シェル変数xに有効な整数定数を形成する値が含まれている場合、オプションで先頭のプラス記号またはマイナス記号を含めると、算術展開「$((x))」および「$(($ x))」は同じ値を返します。値。
ただし、x+1
は変数ではないため、$((x+1))
が展開されることを意味するわけではありません。
別の間接層:
for i in $(eval echo {1..$END}); do
∶
あなたが使用することができます
for i in $(seq $END); do echo $i; done
BSD/OS Xを使用している場合は、seqの代わりにjotを使用できます。
for i in $(jot $END); do echo $i; done
あなたがこれを好むかもしれないよりもプレフィックスが必要な場合
for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done
それがもたらすでしょう
07
08
09
10
11
12
これはbash
では問題なく動作します。
END=5
i=1 ; while [[ $i -le $END ]] ; do
echo $i
((i = i + 1))
done
私はこの質問が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}
これらはすべていいですが、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番目のインスタンスであるべきで、集中的な操作に適しているはずです。
ブレース式の構文にできるだけ近づきたい場合は、 bash-tricks 'range.bash
の range
name__)関数を試してください。
例えば、以下のすべてはecho {1..10}
とまったく同じことをします。
source range.bash
one=1
ten=10
range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}
可能な限り少ない「gotcha」でネイティブbash構文をサポートしようとします。変数がサポートされるだけでなく、無効な範囲が文字列として提供される(たとえばfor i in {1..a}; do echo $i; done
)という望ましくない動作も防止されます。
他の答えはほとんどの場合うまくいくでしょう、しかしそれらはすべて以下の欠点のうちの少なくとも一つを持っています:
seq
name__でさえも、使用するためにインストールしなければならず、bashでロードしなければならず、あなたが期待するプログラムを含んでいなければなりません。いたるところであろうとなかろうと、それは単にBash言語そのものよりも頼りになることの方がはるかに多い。{a..z}
のようなアルファベットの範囲では動作しません。ブレースの拡張はなります。質問は 数 の範囲についてでした、しかし、これは愚痴です。{1..10}
の中括弧で拡張された範囲の構文に似ていないので、両方を使うプログラムは読むのが少し難しいかもしれません。$END
変数が範囲の反対側の有効な範囲「ブックエンド」ではない場合、予期しないことをします。たとえばEND=a
の場合、エラーは発生せず、そのままの値{1..a}
がエコーされます。これはBashのデフォルトの振る舞いでもあります - 予想外のことです。免責事項:私はリンクされたコードの作者です。
これは別の方法です。
end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
{}
を(( ))
に置き換えます。
tmpstart=0;
tmpend=4;
for (( i=$tmpstart; i<=$tmpend; i++ )) ; do
echo $i ;
done
収量:
0
1
2
3
4
あなたがシェルコマンドをやっていて、あなたが(私のように)パイプラインのためのフェチを持っているなら、これは良いことです:
seq 1 $END | xargs -I {} echo {}
ここでいくつかのアイデアを組み合わせて、パフォーマンスを測定しました。
seq
と{..}
は本当に速いですfor
およびwhile
ループが遅い$( )
は遅いですfor (( ; ; ))
ループが遅い$(( ))
はもっと遅いです これらは結論ではありません。結論を出すには、これらのそれぞれの背後にあるCコードを調べなければなりません。これは、コードをループするためにこれらの各メカニズムをどのように使用するのかについての詳細です。ほとんどの単一操作は、ほとんどの場合は問題にならないのと同じ速度であることに十分に近いです。しかしfor (( i=1; i<=1000000; i++ ))
のようなメカニズムは視覚的にわかるように多くの操作です。それはまたあなたがfor i in $(seq 1 1000000)
から得るよりももっとたくさんの操作per loopです。そしてそれはあなたにとって明白でないかもしれません、それでこのようなテストをすることは価値がある理由です。
# 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
これを行うには多くの方法がありますが、私が好むものを以下に示します。
seq
を使う
man seq
の概要
$ seq [-w] [-f format] [-s string] [-t string] [first [incr]] last
構文
フルコマンドseq first incr 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}
を使うここで最初と最後は必須であり、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
これは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"
}
'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でテスト済み