IFSを使用して文字列を分割する


8

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

#!/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

ちなみに、以下の回答のいずれかで問題が解決した場合は、少し時間を取って、左側のチェックマークをクリックして同意してください。これにより、質問に回答済みのマークが付けられ、Stack Exchangeサイトで感謝の気持ちが表現されます。
terdon

回答:


2

修正してください(背景についてはS. Chazelasの回答も参照してください)。

#!/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

ノート:

  • これは、条件付きの入れ方が良いでしょう第二のループをして第一ループ。

  • bashパターン置換("${i//.}".は、要素にがあるかどうかを確認します。(caseステートメントは、OPのコードとは似ていませんが、より単純かもしれません。)

  • read$array入力によるING <<< "${ADDR[3]}"はほど一般的ではありません<<< "$i"どの要素に.s があるかを知る必要がなくなります。

  • このコードでは、「Element: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

1
case $i in (*.*) ...それ$iが含まれていることを確認するためのより標準的な方法になります.(またに移植可能sh)。kshismに興味がある場合は、次も参照してください。[[ $i = *.* ]]
StéphaneChazelas

@StéphaneChazelas、すでに末尾の注記で言及さcaseれていますが、同意します。(OP <<<sh
はと

10

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

以下のような組み込みコマンドをリダイレクトする4.2と前に、中readまたはcommand分割が偶数(4.3という固定)その組み込み用のIFSを取るだろうと、:

$ 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"(なし最後の要素を空に)。

任意の文字列を分割するには、代わりにsplit + 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を1つの空の要素に分割します。

または、適切な分割演算子を使用してシェルを使用します。

  • 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

1

awkのそれはあなたに1つの行の費用になります。

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

同じことがで行うことができるIFS=-. read -r a array <<< "$IN"
ステファンChazelas

@StéphaneChazelas、それは違います。文字列を配列に変換する手順を示しています。しかし、私の1行はすべてをカバーすることに専念しています。フィールドへの分割、処理、出力です。私はあなたの答えと競争していません、それらはただ違う
RomanPerekhrest

0

ここに私の方法:

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

これ$INは、split + glob演算子を呼び出しています。ここでは、glob部分は必要ないので(IN=*-*-/*-17.0.0たとえば、試してみてください)、set -o noglob呼び出す前に実行する必要があります。詳細については、私の回答を参照してください。
ステファンChazelas

1
一般に、「保存」を避け、IFSグローバルに設定することは避けてください。本当にが展開さIFSれたときのfor の値を変更したいだけ$INであり、展開でパス名の展開を実行したくない場合もあります。さらに、OIFS=$IFSIFS空の文字列に設定された場合IFSと完全に設定解除された場合を区別しません。
chepner 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.