bash - 配列 - シェル スクリプト 文字 列 改行 分割




どのように文字列をBashの区切り文字に分割するのですか? (20)

互換性のある回答

この質問には、すでにbashこれを行う方法がたくさんあります。 しかし、bashには多くの特別な機能があります。これはbashism機能するbashismですが、他のshellでは機能しません。

特に、 配列連想配列パターン置換は純粋な境界線であり、他のシェルでは機能しない可能性があります。

Debian GNU / Linuxには、 dashと呼ばれる標準シェルがありますが、私はkshを使いたい人がたくさんいます。

最後に、非常に小さな状況では、彼自身のシェルインタープリタ( ash )を持つbusyboxという特別なツールがあります。

要求された文字列

SOの質問の文字列のサンプルは:

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

これは空白で便利なことがあり、 空白がルーチンの結果を変更する可能性があるため、このサンプル文字列を使用することをお勧めします:

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

bash区切り文字に基づいて文字列を分割する(バージョン> = 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 )に格納され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区切り文字に基づいて文字列を分割する

しかし、多くのシェルの下で使えるものを書くなら、あなたはbashismsを使わないで ください

多くのシェルで、文字列を部分文字列の最初または最後の文字列に分割する構文があります。

${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下で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]>]

楽しむ!

私はこの文字列を変数に格納しています:

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

今私は文字列を分割したいと思い; 私が持っている区切り文字:

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

私は必ずしもADDR1ADDR2変数を必要としADDR1 。 それらがさらに良い配列の要素であれば。

以下の回答からの提案の後、私は私が後にしたもので終わった。

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

私はループでそれを印刷するときに最初の文字列を得ました。


';'で区切られた文字列を分割する1つのライナー。 配列には次のようになります:

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

これはサブシェルにIFSを設定するだけなので、その値の保存と復元について心配する必要はありません。


IFSを設定せずに

コロンが1つだけあれば、それを行うことができます:

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

あなたは:

b = foo
c = bar

bash配列を必要としない2つの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を続ける}}


Internal_field_separator (IFS)変数を設定し、配列に解析できるようにすることができます。 これがコマンドで発生すると、 IFSへの割り当てはその単一コマンドの環境( read )でのみ行われます。 次に、 IFS変数値に従って入力をIFSして配列にします。これを繰り返し実行することができます。

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

;区切られた項目の1行を解析し; それを配列にプッシュします。 入力の1行ごとに区切られた$IN全体を処理するためのもの;

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


あなたの変数に改行が含まれていても動作する、弾丸ではない方法であるBashでは、

IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")

見てください:

$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'

これがうまくいくのは、 read (デリミタ)の-dオプションを空の区切り文字と一緒に使うことです。そのため、 readは、読み込まれたすべてのものを強制的に読み込むように強制されます。 そして、変数の内容を正確にreadます。printfのおかげで改行はありません。 read渡された文字列に末尾の区切り文字が付いていることを確認するために、区切り文字をprintfに入れています。 それがなければ、潜在的な後続の空のフィールドを削除します。

$ in='one;two;three;'    # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

末尾の空のフィールドは保持されます。

Bash≧0.4のための更新

Bash 4.4以降、組み込みのmapfile (別名readarray )は-dオプションをreadarrayて区切り文字を指定します。 したがって、別の標準的な方法は次のとおりです。

mapfile -d ';' -t array < <(printf '%s;' "$in")


ここにきれいな3ライナーです:

in="[email protected];[email protected];[email protected];[email protected]"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

ここでIFSはセパレータに基づいて単語を区切り、 ()arrayを作成するために使用されます。 次に、 [@]を使用して各項目を別々の単語として返します。

それ以降のコードがある場合は、 $IFSを復元する必要もあります( unset IFS


このようなシンプルでスマートな方法があります:

echo "add:sfff" | xargs -d: -i  echo {}

しかし、gnu xargs、BSD xargs cant support -d delimを使用する必要があります。 あなたが私のようにリンゴのMACを使用する場合。 あなたはgnu xargsをインストールすることができます:

brew install findutils

次に

echo "add:sfff" | gxargs -d: -i  echo {}

これが最も簡単な方法です。

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

これは私のために働いた:

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

すぐに処理することを気にしないなら、私はこれをやりたい:

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

この種のループを使って配列を初期化することはできますが、おそらくこれを行う簡単な方法があります。 しかし、これが役立つことを願っています。


すでに提供されている素晴らしい答えとは別に、データを印刷するだけの問題であれば、 awk使用を検討することもできます:

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

フィールドセパレータをに設定し; したがって、 forループを使用してフィールドをループし、それに応じて印刷することができます。

テスト

$ IN="[email protected];[email protected]"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [[email protected]]
> [[email protected]]

別の入力:

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]

大丈夫よ!

ここに私の答えです!

DELIMITER_VAL='='

read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF

SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
   SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
    echo "$i"
done

なぜこのアプローチが私にとって「最高」なのでしょうか?

2つの理由のために:

  1. 区切り文字をエスケープする必要はありませ
  2. 空白に問題はありません。 値は配列内で適切に区切られます!

[]さんの


次のBash / zsh関数は、第1引数を第2引数で指定された区切り文字に分割します。

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を使用して配列に文字列をreadは、bashの-aとzshの-Aフラグが必要です。

必要に応じて、関数を次のようにスクリプトに入れることができます。

#!/usr/bin/env bash

split() {
    # ...
}

split "[email protected]"

私はcutコマンドを参照していくつかの答えを見たことがあるが、それらはすべて削除されている。 私はそれが区切られたログファイルを解析するために、この種のことを行うためのより有用なコマンドの1つだと考えているので、それについて誰も精緻化していないのは少し奇妙です。

この特定の例をbashスクリプト配列に分割する場合、 trはおそらくより効率的ですが、 cutを使用することができ、特定のフィールドを中央から取り出す場合にはより効果的です。

例:

$ echo "[email protected];[email protected]" | cut -d ";" -f 1
[email protected]
$ echo "[email protected];[email protected]" | cut -d ";" -f 2
[email protected]

明らかにそれをループに入れ、-fパラメーターを繰り返して各フィールドを個別に引き出すことができます。

これは、次のような行を持つ区切られたログファイルを持っていると、より便利になります。

2015-04-27|12345|some action|an attribute|meta data

このファイルをcatでき、さらに処理するために特定のフィールドを選択するには、非常に便利です。


配列を使用していない場合、この1つのライナーはどうですか?

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


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変数に与えられたリストは "ハード"でなければならないことに注意してください。

Bashはコマンドと同じ方法で割り当てを処理しないため、IFSを保存して復元する必要があります。代わりの回避策は、関数内で代入をラップし、その関数を変更されたIFSで呼び出すことです。この場合、IFSの個別の保存/復元は不要です。それを指摘するための "Bize"をありがとう。







scripting