空白以外の値でIFSを使用して配列を解析すると、空の要素が生成されます。複数の区切り文字を単一の区切り記号に縮小する
だけでは十分ではありません。たとえば、問題をより明確に説明できます。 IFSを調整して「正常な」結果を得る方法はありますか(IFSの動作を変更する関連設定はありますか?....つまり、デフォルトの空白IFSと同じ動作です)。tr -s
var=" abc def ghi "
echo "============== IFS=<default>"
arr=($var)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
#
sfi="$IFS" ; IFS=':'
set -f # Disable file name generation (globbing)
# (This data won't "glob", but unless globbing
# is actually needed, turn if off, because
# unusual/unexpected combinations of data can glob!
# and they can do it in the most obscure ways...
# With IFS, "you're not in Kansas any more! :)
var=":abc::def:::ghi::::"
echo "============== IFS=$IFS"
arr=($var)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
echo "============== IFS=$IFS and tr"
arr=($(echo -n "$var"|tr -s "$IFS"))
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
set +f # enable globbing
IFS="$sfi" # re-instate original IFS val
echo "============== IFS=<default>"
これが出力です
============== IFS=<default>
# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"
============== IFS=:
# arr[0] ""
# arr[1] "abc"
# arr[2] ""
# arr[3] "def"
# arr[4] ""
# arr[5] ""
# arr[6] "ghi"
# arr[7] ""
# arr[8] ""
# arr[9] ""
============== IFS=: and tr
# arr[0] ""
# arr[1] "abc"
# arr[2] "def"
# arr[3] "ghi"
============== IFS=<default>
答え1
bash
マンページから:
IFS スペースではなく、IFS のすべての文字と隣接する IFS スペース文字が一緒にフィールドを区別します。空白文字の IFS シーケンスも区切り文字と見なされます。
示すIFS スペース(スペース、タブ、改行)は、他の区切り文字とは異なる方法で処理されます。代替区切り記号を使用してまったく同じ動作を得るには、またはtr
次のように区切り文字を置き換えることができますsed
。
var=":abc::def:::ghi::::"
arr=($(echo -n $var | sed 's/ /%#%#%#%#%/g;s/:/ /g'))
for x in ${!arr[*]} ; do
el=$(echo -n $arr | sed 's/%#%#%#%#%/ /g')
echo "# arr[$x] \"$el\""
done
これは%#%#%#%#%
、「固有」(または非常に関連性がないこと)フィールド内の可能な空白を置き換える魔法の値です。フィールドにスペースがないと確信している場合は、この部分を削除してください。
答え2
複数の(空白ではない)連続区切り文字を削除するには、2つの(文字列/配列)パラメータ拡張を使用できます。秘密は、IFS
配列パラメータを拡張するために変数を空の文字列に設定することです。
この内容は次のように記録されます。man bash
下に噴射:
値のないパラメータ拡張により、暗黙的に引用されていないNULLパラメータは削除されます。
(
set -f
str=':abc::def:::ghi::::'
IFS=':'
arr=(${str})
IFS=""
arr=(${arr[@]})
echo ${!arr[*]}
for ((i=0; i < ${#arr[@]}; i++)); do
echo "${i}: '${arr[${i}]}'"
done
)
答え3
gawkを使用してこれを行うこともできますが、ビューには適していません。
var=":abc::def:::ghi::::"
out=$( gawk -F ':+' '
{
# strip delimiters from the ends of the line
sub("^"FS,"")
sub(FS"$","")
# then output in a bash-friendly format
for (i=1;i<=NF;i++) printf("\"%s\" ", $i)
print ""
}
' <<< "$var" )
eval arr=($out)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
出力
# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"
答え4
bash IFSは、連続区切り文字を単一の区切り文字(空白ではなく区切り文字の場合)として扱うための内部メソッドを提供していないため、完全なbashバージョンを設定しました(tr、awk、sedなどの外部呼び出しを使用する比較)。
複数文字のIFSを処理できます。
以下は、このQ / Aページに示されており、オプションの同様のテストでのtr
実行時間の結果ですawk
。テストはアレイの構築(I / Oなし)の10000回の繰り返しに基づいています。
pure bash 3.174s (28 char IFS)
call (awk) 0m32.210s (1 char IFS)
call (tr) 0m32.178s (1 char IFS)
これが出力です
# dlm_str = :.~!@#$%^&()_+-=`}{][ ";></,
# original = :abc:.. def:.~!@#$%^&()_+-=`}{][ ";></,'single*quote?'..123:
# unified = :abc::::def::::::::::::::::::::::::::::'single*quote?'::123:
# max-w 2^ = ::::::::::::::::
# shrunk.. = :abc:def:'single*quote?':123:
# arr[0] "abc"
# arr[1] "def"
# arr[2] "'single*quote?'"
# arr[3] "123"
これはスクリプトです。
#!/bin/bash
# Note: This script modifies the source string.
# so work with a copy, if you need the original.
# also: Use the name varG (Global) it's required by 'shrink_repeat_chars'
#
# NOTE: * asterisk in IFS causes a regex(?) issue, but * is ok in data.
# NOTE: ? Question-mark in IFS causes a regex(?) issue, but ? is ok in data.
# NOTE: 0..9 digits in IFS causes empty/wacky elements, but they're ok in data.
# NOTE: ' single quote in IFS; don't know yet, but ' is ok in data.
#
function shrink_repeat_chars () # A 'tr -s' analog
{
# Shrink repeating occurrences of char
#
# $1: A string of delimiters which when consecutively repeated and are
# considered as a shrinkable group. A example is: " " whitespace delimiter.
#
# $varG A global var which contains the string to be "shrunk".
#
# echo "# dlm_str = $1"
# echo "# original = $varG"
dlms="$1" # arg delimiter string
dlm1=${dlms:0:1} # 1st delimiter char
dlmw=$dlm1 # work delimiter
# More than one delimiter char
# ============================
# When a delimiter contains more than one char.. ie (different byte` values),
# make all delimiter-chars in string $varG the same as the 1st delimiter char.
ix=1;xx=${#dlms};
while ((ix<xx)) ; do # Where more than one delim char, make all the same in varG
varG="${varG//${dlms:$ix:1}/$dlm1}"
ix=$((ix+1))
done
# echo "# unified = $varG"
#
# Binary shrink
# =============
# Find the longest required "power of 2' group needed for a binary shrink
while [[ "$varG" =~ .*$dlmw$dlmw.* ]] ; do dlmw=$dlmw$dlmw; done # double its length
# echo "# max-w 2^ = $dlmw"
#
# Shrik groups of delims to a single char
while [[ ! "$dlmw" == "$dlm1" ]] ; do
varG=${varG//${dlmw}$dlm1/$dlm1}
dlmw=${dlmw:$((${#dlmw}/2))}
done
varG=${varG//${dlmw}$dlm1/$dlm1}
# echo "# shrunk.. = $varG"
}
# Main
varG=':abc:.. def:.~!@#$%^&()_+-=`}{][ ";></,'\''single*quote?'\''..123:'
sfi="$IFS"; IFS=':.~!@#$%^&()_+-=`}{][ ";></,' # save original IFS and set new multi-char IFS
set -f # disable globbing
shrink_repeat_chars "$IFS" # The source string name must be $varG
arr=(${varG:1}) # Strip leading dlim; A single trailing dlim is ok (strangely
for ix in ${!arr[*]} ; do # Dump the array
echo "# arr[$ix] \"${arr[ix]}\""
done
set +f # re-enable globbing
IFS="$sfi" # re-instate the original IFS
#
exit