IFSを使用した文字列の分割

IFSを使用した文字列の分割

文字列を分割するサンプルスクリプトを作成しましたが、期待どおりに機能しません。

#!/bin/bash
IN="One-XX-X-17.0.0"
IFS='-' read -r -a ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    echo "Element:$i"
done
#split 17.0.0 into NUM
IFS='.' read -a array <<<${ADDR[3]};
for element in "${array[@]}"
do
    echo "Num:$element"
done
  • 実際の出力
    One
    XX
    X
    17.0.0
    17 0 0
    
  • しかし、私の予想結果は次のとおりです。
    One
    XX
    X
    17.0.0
    17
    0
    0
    

答え1

以前のバージョンでは、bash後で変数を引用する必要がありました<<<。この問題は4.4で修正されました。以前のバージョンでは、変数はIFSで分割され、結果の単語がリダイレクトを<<<構成する一時ファイルに格納される前に空間的にリンクされていました。

4.2以前では、readまたは同じ組み込み関数をリダイレクトするときに、分割commandがその組み込み関数のIFSも使用しました(4.3ではこの問題は修正されました)。

$ bash-4.2 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo  "$x"'
a b c d
$ bash-4.2 -c 'a=a.b.c.d; IFS=. cat <<< $a'
a.b.c.d
$ bash-4.2 -c 'a=a.b.c.d; IFS=. command cat <<< $a'
a b c d

この問題は4.3で解決されました。

$ bash-4.3 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo  "$x"'
a.b.c.d

しかし、$aそれでも単語分割の影響を受けます。

$ bash-4.3 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo  "$x"'
a b c d

4.4から:

$ bash-4.4 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo  "$x"'
a.b.c.d

以前のバージョンに移植するには、変数を引用してください(または最初zshに変数ソースを使用しても問題はありません)。<<<

$ bash-any-version -c 'a=a.b.c.d; IFS=.; read x <<< "$a"; echo "$x"'
a.b.c.d

文字列を分割するこの方法は、改行文字を含まない文字列に対してのみ機能します。また、、、、a..b.c.に分割されます(最後の要素は空ではありません)。"a""""b""c"

任意の文字列を分割するには、分割+glob演算子を使用できます(これは標準になり、以前と同様に変数の内容を一時ファイルに保存する必要はありません<<<)。

var='a.new
line..b.c.'
set -o noglob # disable glob
IFS=.
set -- $var'' # split+glob
for i do
  printf 'item: <%s>\n' "$i"
done

または:

array=($var'') # in shells with array support

これは''、末尾の空の要素(存在する場合)を保存するためのものです。また、空の要素を$var空の要素に分割します。

または、適切な分割演算子でシェルを使用してください。

  • zsh:

    array=(${(s:.:)var} # removes empty elements
    array=("${(@s:.:)var}") # preserves empty elements
    
  • rc:

    array = ``(.){printf %s $var} # removes empty elements
    
  • fish

    set array (string split . -- $var) # not for multiline $var
    

答え2

修理(また参照S.Chazerasの答え背景用)、合理的な出力:

#!/bin/bash
IN="One-XX-X-17.0.0"
IFS='-' read -r -a ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    if [ "$i" = "${i//.}" ] ; then 
        echo "Element:$i" 
        continue
    fi
    # split 17.0.0 into NUM
    IFS='.' read -a array <<< "$i"
    for element in "${array[@]}" ; do
        echo "Num:$element"
    done
done

出力:

Element:One
Element:XX
Element:X
Num:17
Num:0
Num:0

メモ:

  • 条件を追加することをお勧めします2位リング存在するこれ初めてリングの形。

  • bashパターン置換()は"${i//.}"要素に存在することを確認します。.caseドアは次のようになりますが、より簡単にすることができます。OPパスワード。 )

  • read$array入力によるingは<<< "${ADDR[3]}"ingほど汎用性がありません<<< "$i"。知る必要がなくなるどの要素には.sがあります。

  • このコードは「要素: 17.0.0「意図しないことでした。はい意図的にメインループを次のように交換します。

    for i in "${ADDR[@]}"; do
       echo "Element:$i" 
       if [ "$i" != "${i//.}" ] ; then 
       # split 17.0.0 into NUM
           IFS='.' read -a array <<< "$i"
           for element in "${array[@]}" ; do
               echo "Num:$element"
           done
       fi
    done
    

答え3

そしてアッ一行のコストが発生します。

IN="One-XX-X-17.0.0"

awk -F'[-.]' '{ for(i=1;i<=NF;i++) printf "%s : %s\n",($i~/^[0-9]+$/?"Num":"Element"),$i }' <<<"$IN"
  • -F'[-.]'-- 私たちの場合は、複数の文字に基づくフィールド区切り記号です。.

出力:

Element : One
Element : XX
Element : X
Num : 17
Num : 0
Num : 0

答え4

これが私がする方法です:

OIFS=$IFS
IFS='-'
IN="One-XX-X-17.0.0"
ADDR=($IN)
for i in "${ADDR[@]}"; do
 echo "Element:$i"
done
IFS='.'
array=(${ADDR[3]})
for element in "${array[@]}"
do
  echo "Num:$element"
done

結果は予想通りです。

Num:17
Num:0
Num:0

関連情報