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]"
私は必ずしもADDR1
とADDR2
変数を必要とし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"
set
組み込みを使用して[email protected]
配列をロードします:
IN="[email protected];[email protected]"
IFS=';'; set $IN; IFS=$' \t\n'
その後、パーティーを始める:
echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2
あなたの変数に改行が含まれていても動作する、弾丸ではない方法である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")
あなたは多くの状況にawkを適用することができます
echo "[email protected];[email protected]"|awk -F';' '{printf "%s\n%s\n", $1, $2}'
あなたもこれを使うことができます
echo "[email protected];[email protected]"|awk -F';' '{print $1,$2}' OFS="\n"
ここにきれいな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つの理由のために:
- 区切り文字をエスケープする必要はありません 。
- 空白には問題はありません。 値は配列内で適切に区切られます!
[]さんの
次の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]"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
echo $entry
done
出力
[email protected]
[email protected]
システム:Ubuntu 12.04.1
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"をありがとう。