bash - variable - shell script while loop




如何在Bash中的分隔符上分割字符串? (20)

我有這個字符串存儲在一個變量中:

IN="[email protected];[email protected]"

現在我想分割字符串; 分隔符,以便我有:

ADDR1="[email protected]"
ADDR2="[email protected]"

我不一定需要ADDR1ADDR2變量。 如果它們是更好的數組的元素。

根據下面的答案提出建議後,我結束了以下的工作:

#!/usr/bin/env bash

IN="[email protected];[email protected]"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

輸出:

> [[email protected]]
> [[email protected]]

有一個涉及設置Internal_field_separator (IFS)的解決方案; 。 我不確定該答案發生了什麼,您如何將IFS重置為默認值?

RE: IFS解決方案,我試過了,它工作,我保留舊的IFS ,然後恢復它:

IN="[email protected];[email protected]"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

順便說一句,當我嘗試

mails2=($IN)

在循環打印時,我只有第一個字符串,沒有$IN左右括號。


兼容答案

對於這個問題,在bash已經有很多不同的方法來做到這一點。 但bash有很多特殊功能,所謂的bashism良好,但在其他shell不起作用。

特別是, 數組關聯數組模式替換都是純粹的bashisms,並且可能無法在其他shell中工作。

在我的Debian GNU / Linux上 ,有一個叫做dash的標準 shell,但我知道很多人喜歡使用ksh 。

最後,在非常小的情況下,有一個叫做busybox的特殊工具,帶有自己的shell解釋器( ash )。

請求的字符串

SO問題中的字符串示例是:

IN="[email protected];[email protected]"

由於這可能對空格有用,而且空格可能會修改例程的結果,所以我更喜歡使用此示例字符串:

 IN="[email protected];[email protected];Full Name <[email protected]>"

根據bash分隔符分割字符串(version> = 4.2)

純粹的 bash下,我們可以使用數組IFS

var="[email protected];[email protected];Full Name <[email protected]>"

oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS

IFS=\; read -a fields <<<"$var"

在最近的bash下使用這個語法不會改變當前會話的$IFS ,但僅限於當前命令:

set | grep ^IFS=
IFS=$' \t\n'

現在字符串var被拆分並存儲到一個數組(名為fields )中:

set | grep ^fields=\\\|^var=
fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
var='[email protected];[email protected];Full Name <[email protected]>'

我們可以使用declare -p來請求可變內容:

declare -p var fields
declare -- var="[email protected];[email protected];Full Name <[email protected]>"
declare -a fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")

read是進行拆分的最快方式,因為沒有叉子 ,也沒有外部資源調用。

從那裡,您可以使用您已知的語法來處理每個字段:

for x in "${fields[@]}";do
    echo "> [$x]"
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

或者在處理之後丟棄每個字段(我喜歡這種轉換方法):

while [ "$fields" ] ;do
    echo "> [$fields]"
    fields=("${fields[@]:1}")
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

甚至可以用於簡單的打印輸出(較短的語法):

printf "> [%s]\n" "${fields[@]}"
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

根據shell分隔符分割字符串

但是如果你會寫很多可用的shell,你不得不使用bashisms

在許多shell中有一種語法用於在第一次最後一次出現的子字符串之間分割字符串:

${var#*SubStr}  # will drop begin of string up to first occur of `SubStr`
${var##*SubStr} # will drop begin of string up to last occur of `SubStr`
${var%SubStr*}  # will drop part of string from last occur of `SubStr` to the end
${var%%SubStr*} # will drop part of string from first occur of `SubStr` to the end

(這是我的答案出版物的主要原因;)

正如Score_Under指出的Score_Under :

#%刪除最短的匹配字符串,和

##%%刪除最長的可能。

這個小示例腳本在bash , dash , ksh , busybox下運行良好,並且在Mac-OS的bash下也進行了測試:

var="[email protected];[email protected];Full Name <[email protected]>"
while [ "$var" ] ;do
    iter=${var%%;*}
    echo "> [$iter]"
    [ "$var" = "$iter" ] && \
        var='' || \
        var="${var#*;}"
  done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

玩的開心!


Maybe not the most elegant solution, but works with * and spaces:

IN="[email protected] me.com;*;[email protected]"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

Outputs

> [[email protected] me.com]
> [*]
> [[email protected]]

Other example (delimiters at beginning and end):

IN=";[email protected] me.com;*;[email protected];"
> []
> [[email protected] me.com]
> [*]
> [[email protected]]
> []

Basically it removes every character other than ; making delims eg. ;;; 。 Then it does for loop from 1 to number-of-delimiters as counted by ${#delims} . The final step is to safely get the $i th part using cut .



下面的Bash / zsh函數將第一個參數分割為由第二個參數給定的分隔符:

split() {
    local string="$1"
    local delimiter="$2"
    if [ -n "$string" ]; then
        local part
        while read -d "$delimiter" part; do
            echo $part
        done <<< "$string"
        echo $part
    fi
}

例如,命令

$ split 'a;b;c' ';'

產量

a
b
c

例如,這個輸出可以被傳送給其他命令。 例:

$ split 'a;b;c' ';' | cat -n
1   a
2   b
3   c

與其他解決方案相比,這個解決方案具有以下優點:

  • IFS未被覆蓋:由於即使是局部變量的動態範圍限制,覆蓋循環中的IFS也會導致新值洩漏到循環內執行的函數調用中。

  • 不使用數組:使用read將字符串讀入數組需要在Bash中使用-a標誌,在zsh使用-A

如果需要,可以按如下方式將該函數放入腳本中:

#!/usr/bin/env bash

split() {
    # ...
}

split "[email protected]"

兩個都不需要bash數組的bourne-ish選擇:

案例1 :保持簡潔:使用NewLine作為記錄分隔符...例如。

IN="[email protected]
[email protected]"

while read i; do
  # process "$i" ... eg.
    echo "[email:$i]"
done <<< "$IN"

注意:在第一種情況下,沒有任何子流程可以協助列表操作。

想法:也許值得在內部廣泛使用NL,並且在外部產生最終結果時僅轉換為不同的RS。

案例2 :使用“;” 作為記錄分隔符...例如。

NL="
" IRS=";" ORS=";"

conv_IRS() {
  exec tr "$1" "$NL"
}

conv_ORS() {
  exec tr "$NL" "$1"
}

IN="[email protected];[email protected]"
IN="$(conv_IRS ";" <<< "$IN")"

while read i; do
  # process "$i" ... eg.
    echo -n "[email:$i]$ORS"
done <<< "$IN"

在這兩種情況下,可以在循環內組成一個子列表,在循環完成後持久化。 這在處理內存中的列表時非常有用,而不是將列表存儲在文件中。 {ps保持冷靜並繼續B-)}


在Android shell中,大多數建議的方法都不起作用:

$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

什麼工作是:

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin

//表示全局替換。


如果你不介意處理它們,我喜歡這樣做:

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

你可以使用這種循環來初始化一個數組,但是可能有更簡單的方法來完成它。 但希望這有助於。


如果你不使用數組,那麼這個班輪怎麼樣?

IFS=';' read ADDR1 ADDR2 <<<$IN


您可以設置Internal_field_separator (IFS)變量,然後讓它解析成一個數組。 當這種情況發生在一個命令中時, IFS的分配只發生在單個命令的環境( read )上。 然後它根據IFS變量值將輸入解析為一個數組,然後我們可以迭代。

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    # process "$i"
done

它將解析由一行分隔的項目; ,將其推入數組中。 用於處理整個$IN ,每次用一行輸入分隔;

 while IFS=';' read -ra ADDR; do
      for i in "${ADDR[@]}"; do
          # process "$i"
      done
 done <<< "$IN"


採用Bash shell腳本分割數組

IN="[email protected];[email protected]"
arrIN=(${IN//;/ })

說明:

這種構造取代了所有的';' (最初的//表示全局替換)與字符串IN (單個空格),然後將空格分隔的字符串解釋為一個數組(這是括號括起來的)。

花括號內用來替換每個';'的語法 帶有' '字符的字符稱為參數擴展

有一些常見的陷阱:

  1. 如果原始字符串有空格,則需要使用IFS
    • IFS=':'; arrIN=($IN); unset IFS;
  2. 如果原始字符串中包含空格分隔符是新行,則可以使用以下內容設置IFS
    • IFS=$'\n'; arrIN=($IN); unset IFS;

沒有設置IFS

如果你只有一個冒號,你可以這樣做:

a="foo:bar"
b=${a%:*}
c=${a##*:}

你會得到:

b = foo
c = bar

用單引號分隔用';'分隔的字符串 成一個數組是:

IN="[email protected];[email protected]"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

這只能在一個子shell中設置IFS,因此您不必擔心保存和恢復其值。


這對我有效:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

這是最簡單的方法。

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}


這裡有一些很酷的答案(尤其是errator),但是對於類似於其他語言中的分裂的東西 - 這就是我原來的問題所表達的意思 - 我在這裡解決了這個問題:

IN="[email protected];[email protected]"
declare -a a="(${IN/;/ })";

現在${a[0]}${a[1]}等等,就像你期望的那樣。 使用${#a[*]}作為條款數量。 當然也可以迭代:

for i in ${a[*]}; do echo $i; done

重要的提示:

這適用於沒有空間擔心的情況,這解決了我的問題,但可能無法解決您的問題。 在這種情況下,使用$IFS解決方案。



IN='[email protected];[email protected];Charlie Brown <[email protected];!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

輸出:

[email protected]
[email protected]
Charlie Brown <[email protected]
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

說明:使用括號()進行簡單賦值,可以將分號分隔的列表轉換為數組,前提是您擁有正確的IFS。 標準FOR循環像往常一樣處理該數組中的單個項目。 請注意,給IN變量的列表必須是“硬”引用的,也就是說,使用單個刻度。

IFS必須被保存並且被恢復,因為Bash並不像命令那樣對待一個賦值。 另一種解決方法是將分配包裝在一個函數中,並使用修改後的IFS調用該函數。 在這種情況下,不需要單獨保存/恢復IFS。 感謝“Bize”的指出。





scripting