Bash:如何消除循環生成的變數末尾的逗號

Bash:如何消除循環生成的變數末尾的逗號

我有以下 bash 腳本,它在同一行輸出兩個隨機數。

#!/bin/bash

for i in 1 2; do
   unset var
   until [ "$var" -lt 10000 ] 2>/dev/null; do
      var="$RANDOM"
done
printf "%s," "${var/%,/}"
done

輸出是:

5751,2129,

我試著去掉$var我嘗試過的末尾的逗號"${var/%,/}",以便 $var = 5751,2129 的輸出可以使用。有人能幫忙嗎?

答案1

像這樣分配後:var="$RANDOM"var變數保存來自 的擴展的字串$RANDOM。在 Bash 中擴展為從到$RANDOM範圍內的十進制數。那裡沒有角色,所以嘗試從以後的擴展中刪除是沒有意義的。032767,,$var

您在輸出中觀察到的逗號字元來自這部分程式碼:

printf "%s," "${var/%,/}"
# here    ^

在循環的每次迭代中都會呼叫此命令,因此每次迭代都會向輸出添加一個逗號字元。

已列印的內容無法取消列印。有細微差別:

  • 您可以將輸出透過管道傳輸到過濾器,過濾器可能會刪除其中的某些部分。過濾器可能位於腳本之外(例如您呼叫the_script | the_filter);或者你可以透過管道輸出一些部分腳本到腳本內的過濾器。在後一種情況下,整個腳本的輸出將被過濾;腳本部分已經列印的內容仍然沒有被列印;我是認真的到達過濾器。過濾器稍後將其刪除。
  • 如果您列印到終端,則可以使用新資料覆蓋先前輸出的某些部分。有字元和序列可以移動遊標;但他們還是都到達航廈了。您幾乎可以立即在視覺上隱藏先前的輸出,但是如果您將輸出重定向到檔案(或不理解所使用序列的終端),那麼您會發現它全部在那裡。

消除不需要的逗號的正確方法是首先不要列印它;或在腳本內過濾它。有多種方法可以做到這一點,但我無意找到所有這些方法。我將討論其中的一些。我假設您想了解具有兩次以上迭代的循環的可能方法;可能是 for 循環,其中迭代次數事先未知;也許 for 迴圈不是用數字枚舉的;也許 for 迴圈可能永遠不會完成(例如while true代替for)。

注意:您使用了printf "%s," "${var/%,/}",它不列印尾隨換行符。如果可能的話,我會嘗試複製這種行為。

幾種可能的方法:

  1. 循環內部不依賴$i.您可以擺脫循環並使用兩個單獨的變數:

    unset var1
    until [ "$var1" -lt 10000 ] 2>/dev/null; do
       var1="$RANDOM"
    done
    unset var2
    until [ "$var2" -lt 10000 ] 2>/dev/null; do
       var2="$RANDOM"
    done
    printf '%s,%s' "$var1" "$var2"
    

    筆記:

    • 它不是乾燥
    • 它的擴展性不好。 (如果你有怎麼辦for i in {1..100}?)
    • 如果事先不知道迭代次數,就會變得很麻煩。
  2. 您可以將當前程式碼放入管道中並過濾掉結尾的逗號。例子:

    for i in 1 2; do
       unset var
       until [ "$var" -lt 10000 ] 2>/dev/null; do
          var="$RANDOM"
       done
    printf "%s," "$var"
    done | sed 's/,$//'
    

    筆記:

    • sed(或您使用的任何篩選器)可能會也可能不會處理循環產生的不完整行(未以換行符號終止的行)。如果是sed這樣,取決於實施情況。
    • 如果sed確實處理了它,它仍然可能以換行符號終止其輸出。
    • sed(或您使用的任何過濾器)可能無法處理太長的行。上面的特定程式碼產生了相當短的行,但通常(想像許多迭代)長度可能是一個問題。
    • sed作為面向行的工具,在處理之前必須讀取整行。在這種情況下,它必須讀取其整個輸入。在所有迭代完成之前,您不會從中得到任何東西。
    • 此循環在子 shell 中運行。在一般情況下,無論出於何種原因,您可能希望它在主 shell 中運行。
  3. 您可以將目前程式碼的輸出擷取到變數中。最後,在擴展變數時刪除尾隨逗號:

    capture="$(for i in 1 2; do
       unset var
       until [ "$var" -lt 10000 ] 2>/dev/null; do
          var="$RANDOM"
       done
    printf "%s," "$var"
    done)"
    printf "%s" "${capture%,}"
    

    筆記:

    • 此循環在子 shell 中運行。在一般情況下,無論出於何種原因,您可能希望它在主 shell 中運行。
    • 該程式碼在到達最後一個printf(循環之外)之前保持沉默。在所有迭代完成之前你不會得到任何東西。
    • 一般來說,循環可以輸出任意數量的位元組。我認為 Bash 可以在變數中儲存大量資料;然後,因為printf是內建的,所以它可以大概手柄printf "%s" "${capture%,}"不碰命令列長度的限制。我還沒有對此進行徹底測試,因為 IMO 在 shell 變數中儲存大量資料無論如何都不是最佳實踐。如果您知道輸出的長度有限,那麼這種做法仍然是合理的。 (鄭重聲明:上面的程式碼肯定會產生非常短的輸出。)
    • Bash 無法在變數中儲存 NUL 字元(大多數 shell 不能;zsh 可以)。另外$()刪除所有尾隨換行符。這意味著您不能使用變數來存儲隨意的輸出並稍後準確地再現它。 (鄭重聲明:在上面的程式碼中,內部片段$()不會產生 NUL 或尾隨換行符。)
  4. 您可以將每次迭代附加到某個變量,而不是捕獲輸出:

    capture=''
    for i in 1 2; do
       unset var
       until [ "$var" -lt 10000 ] 2>/dev/null; do
          var="$RANDOM"
       done
    capture="$capture$var,"
    done
    printf "%s" "${capture%,}"
    

    筆記:

    • 程式碼在主 shell 中運行(而不是在子 shell 中)。
    • 在變數中儲存資料的限制(請參閱前面的方法)仍然適用。
    • 在 Bash 中,您可以使用 附加到變數capture+="$var,"。 (注意:如果變數設定了整數屬性則=+意思是“添加”,而不是“附加”.)
  5. 您可以偵測最後一次迭代並使用不含以下內容的格式,

    # this example is more educative with more than two iterations
    for i in {1..5}; do
       unset var
       until [ "$var" -lt 10000 ] 2>/dev/null; do
          var="$RANDOM"
       done
       if [ "$i" -eq 5 ]; then
          printf "%s" "$var"
       else 
          printf "%s," "$var"
       fi
    done
    

    筆記:

    • 沒有子殼。
    • 如果您事先不知道數字,則檢測最後一次迭代會更加困難。
    • 如果您迭代數組(例如for i in "${arr[@]}"),那就更難了。
    • 每次迭代都會立即列印,您會按順序獲得輸出。即使循環是無限的,這也會起作用。
  6. 您可以檢測第一次迭代並使用不帶,.請注意,您可以在原始程式碼中使用,%s而不是;%s,那你會得到,5751,2129而不是5751,2129,.透過此更改,任何上述避免或刪除尾隨逗號的方法都可以轉換為避免或刪除前導逗號的方法。最後一個方法變成:

    # this example is more educative with more than two iterations
    for i in {1..5}; do
       unset var
       until [ "$var" -lt 10000 ] 2>/dev/null; do
          var="$RANDOM"
       done
       if [ "$i" -eq 1 ]; then
          printf "%s" "$var"
       else 
          printf ",%s" "$var"
       fi
    done
    

    筆記:

    • 沒有子殼。
    • 1如果您始終以(或一般固定的唯一字串)開始,則檢測第一次迭代很容易。
    • 但如果你迭代一個數組,例如 ,那就更難了for i in "${arr[@]}"。您不應該檢查,if [ "$i" = "${arr[1]}" ]因為可能存在與"${arr[1]}"數組中後面的元素相同的元素。處理這個問題的一個簡單方法是保留循環的索引(index=1在循環之前,然後在每次迭代結束時加一)並根據 ; 測試其值1。不過我會發現這樣的程式碼有點麻煩。
    • 每次迭代都會立即列印,您會按順序獲得輸出。即使循環是無限的,這也會起作用。
  7. 您可以使其,自身來自變數。進入循環,變數為空,並,在每次迭代結束時將其設為 。這將在結束時有效地更改該值一次第一的迭代。例子:

    # this example is more educative with more than two iterations
    separator=''
    for i in {1..5}; do
       unset var
       until [ "$var" -lt 10000 ] 2>/dev/null; do
          var="$RANDOM"
       done
       printf "%s%s" "$separator" "$var"
       separator=,
    done
    

    筆記:

    • 沒有子殼。
    • 即使迭代數組也能很好地工作。
    • 每次迭代都會立即列印,您會按順序獲得輸出。即使循環是無限的,這也會起作用。

    我個人覺得這個方法非常優雅。

一般注意事項:

  • 每個片段都會產生沒有尾隨換行符的輸出(帶有或另一個過濾器的輸出可能是例外sed)。如果您需要整個輸出來形成正確終止的文字行,請在循環後執行printf '\n'(或sole )。echo
  • 你想,成為一個分隔符,而不是終結者。這意味著如果$var在迭代中擴展為空字串,則精確零次迭代的循環將產生與精確一次迭代的循環相同的空輸出。在我們的例子中,$var每次都會擴展為非空字串,並且我們知道迭代次數不只零次;但在一般情況下,使用分隔符號而不是終止符可能會導致歧義。

相關內容