bash - loop - shell script for 100




如何遍歷Bash中由變量定義的一系列數字? (12)

當範圍由變量給出時,如何迭代Bash中的一系列數字?

我知道我可以做到這一點(在Bash documentation稱為“序列表達式”):

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

這使:

1
2
3
4

然而,我怎樣才能用變量替換範圍端點? 這不起作用:

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

打印:

{} 1..5


討論

正如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的整個手冊頁(就像我用Korn shell( 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手冊。 可以用一個沒有exec()的單一(內部) fork()來克服這個障礙(就像調用seq ,這是另一個圖像需要fork + exec一樣):

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

evalecho都是Bash buildins,但是命令替換( $(…)構造)需要fork() )。


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將它們視為算術。


另一層間接:

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

如果你想盡可能的接近括號表達式的語法,可以嘗試使用bash- range.bashrange函數

例如,以下所有內容都與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 )也被阻止。

其他答案在大多數情況下都能正常工作,但它們至少有下列缺點之一:

  • 它們中的許多使用subshells ,這會損害性能,並且在某些系統上可能無法實現
  • 他們中的許多人依靠外部程序。 即使seq是必須安裝使用的二進製文件,必須通過bash加載,並且必須包含您期望的程序,因為它在這種情況下起作用。 無處不在,依靠的不僅僅是Bash語言本身。
  • 只使用本地Bash功能的解決方案(如@ ephemient's)不適用於字母範圍,如{a..z} ; 大括號擴展會。 但問題是關於數字的範圍,所以這是一個狡辯。
  • 它們中的大多數與{1..10}大括號擴展範圍語法在{1..10}上不相似,所以使用這兩種語法的程序可能會比較難以閱讀。
  • @ bobbogo的答案使用了一些熟悉的語法,但是如果$END變量不是范圍另一端的有效範圍“書擋”,那麼會出乎意料。 例如,如果END=a ,則不會發生錯誤,並且逐字值{1..a}會被回顯。 這也是Bash的默認行為 - 它通常是意想不到的。

免責聲明:我是鏈接代碼的作者。


如果你需要它的前綴,你可能會喜歡這個

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

那將會屈服

07
08
09
10
11
12

如果您使用BSD / OS X,則可以使用jot而不是seq:

for i in $(jot $END); do echo $i; 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}

(( ))替換{}

tmpstart=0;
tmpend=4;

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

產量:

0
1
2
3
4

這在bash正常工作:

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

這在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"
}

這是另一種方式:

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

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

編輯:我更喜歡seq在其他方法,因為我實際上可以記住它;)







shell