web-dev-qa-db-ja.com

Bashの変数で定義された数の範囲でどうやって反復するのですか?

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

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

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

1407
Jiaaro

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はそれらを算術として扱います。

362
ephemient

討論

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

evalechoはどちらもBashの組み込み関数ですが、コマンド置換にはfork()が必要です($(…)構文)。

172
tzot

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

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-ismsをスクリプトに入れません。スクリプトはもっと速いシェル、もっと安全なもの、もっと埋め込まれたスタイルのものが必要かもしれません。彼らは/ bin/shとしてインストールされているものなら何でも実行する必要があるかもしれません、そしてそれからすべての通常のプロ標準の議論があります。 Shellshock、 aka bashdoorを覚えていますか?
93
DigitalRoss

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+1POSIX 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
    ∶
30
bobbogo

あなたが使用することができます

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

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

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

あなたがこれを好むかもしれないよりもプレフィックスが必要な場合

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

それがもたらすでしょう

07
08
09
10
11
12
17
hossbear

これはbashでは問題なく動作します。

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

私はこの質問が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}
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番目のインスタンスであるべきで、集中的な操作に適しているはずです。

6
SuperBob

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

例えば、以下のすべては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)という望ましくない動作も防止されます。

他の答えはほとんどの場合うまくいくでしょう、しかしそれらはすべて以下の欠点のうちの少なくとも一つを持っています:

  • それらの多くは_ {(サブシェル を使いますが、これはシステムによっては パフォーマンスに悪影響を及ぼす不可能かもしれません を可能にします。
  • それらの多くは外部プログラムに依存しています。たとえseqname__でさえも、使用するためにインストールしなければならず、bashでロードしなければならず、あなたが期待するプログラムを含んでいなければなりません。いたるところであろうとなかろうと、それは単にBash言語そのものよりも頼りになることの方がはるかに多い。
  • @ ephemientのようにネイティブのBash機能のみを使用するソリューションは、{a..z}のようなアルファベットの範囲では動作しません。ブレースの拡張はなります。質問はの範囲についてでした、しかし、これは愚痴です。
  • それらのほとんどは視覚的には{1..10}の中括弧で拡張された範囲の構文に似ていないので、両方を使うプログラムは読むのが少し難しいかもしれません。
  • @ bobbogoの答えはよく知られた構文のいくつかを使用しますが、$END変数が範囲の反対側の有効な範囲「ブックエンド」ではない場合、予期しないことをします。たとえばEND=aの場合、エラーは発生せず、そのままの値{1..a}がエコーされます。これはBashのデフォルトの振る舞いでもあります - 予想外のことです。

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

6
Zac B

これは別の方法です。

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

{}(( ))に置き換えます。

tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
echo $i ;
done

収量:

0
1
2
3
4
5
BashTheKeyboard

あなたがシェルコマンドをやっていて、あなたが(私のように)パイプラインのためのフェチを持っているなら、これは良いことです:

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

5
Alex Spangher

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

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)から得るよりももっとたくさんの操作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
4
Bruno Bronosky

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

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}を使う

ここで最初と最後は必須であり、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
0
theBuzzyCoder

これは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"
}
0
Ethan Post

'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
Zimba