シェルスクリプトを作成するときに文字列を分割したい場合がよくあります。以下は非常に簡単な例です。
for dir in $(echo $PATH | tr : " "); do
[[ -x "$dir"/"$1" ]] && echo $dir
done
$1
非常にシンプルでうまくいきますが、$ PATHのディレクトリ名にスペースが含まれていると中断されます。
ループ区切り文字が発生したときに文字列を分割するための推奨方法は何ですか?
理想的には、このソリューションは(かなり)古いシェル(ksh88など)で動作します。
答え1
確実な解決策はシェルワード分割を使用することですが、注意すべきいくつかの問題があります。
IFS=:
set -o noglob
for dir in $PATH''; do
dir=${dir:-.}
[ -x "${dir%/}/$1" ] && printf "%s\n" "$dir"
done
set -o noglob
変数が引用されていない場合は両方が必要です。噴射そしてファイル名の生成(ワイルドカード)それを実行するにはここに必要です噴射$PATH
(たとえば、含まれている可能性のないイベントの場合や...以外の/usr/local/*bin*
フォルダで検索したい、含まれている場合はコンピュータを停止したくありません。)/usr/local/*bin*
/usr/local/bin
/usr/local/sbin
PATH
/*/*/*/../../../*/*/*/*/../../../*/*/*/*
空$PATH
のコンポーネントは現在のディレクトリ(.
)を表し、この場合は正しくありません/
。$dir/$1
この場合、回避策はを使用するとき($dir${dir:+/}$1
を書き込むか変更することです)。$dir
.
printf '%s\n' "$dir"
//foo
必ずしもと同じである必要はないので にあれば/foo
にある必要はなく、これです。したがって、末尾のスラッシュを削除します。/
$PATH
$dir/$1
//$1
${dir%/}
その後、いくつかの他の質問があります。
の場合はフィールドです$PATH
。":"
仕切りそしての場合は$IFS
フィールドです。ターミネーター(はい、わかりましたS
。S分割ツール, ksh 動作を標準化する ksh と POSIX による)
したがって、$PATH
これは/usr/bin:/bin:
(悪い習慣ですが依然として一般的な場合)、and(つまり現在のディレクトリ)を意味し、シェルワードの分割(以外のすべてのPOSIXシェル"/usr/bin"
)はandにのみ分割されます。"/bin"
""
zsh
/usr/bin
/bin
設定されているが$PATH
空の場合は、次のことを意味します。「現在のディレクトリでのみ検索」。そして、シェル(区切り文字として扱うシェルを含む$IFS
)はそれを空のリストに展開します。
''
上記を追加すると、$PATH
両方の問題が解決されます。
最後に重要なのは。設定されていない場合は$PATH
特別な意味があります。システム基本検索リストの表示、残念ながら誰に(どんな命令を)尋ねるかによって意味が変わります。
$ env -u PATH bash -c 'type usbipd'
usbipd is /usr/local/sbin/usbipd
$ env -u PATH ksh -c 'type usbipd'
ksh: whence: usbipd: not found
デフォルトでは、スクリプトは自分にとって重要な文脈でデフォルトの検索パスが何であるかを推測する必要があります。
設定されていないか空の場合、POSIXは指定されていない動作を維持するので$PATH
役に立ちません。これはまた、上記の内容が過去、現在、または将来のPOSIX / Unixシステムには適用されない可能性があることを意味します。
簡単に言えば、$PATH
コマンドが実行された場所を特定するために解析するのは難しい作業です。
次の標準コマンドがありますcommand
。
ls_path=$(command -v ls)
しかし、人々はこう尋ねることができます。なぜ知りたいですか?
これで IFS をデフォルト値に復元します。
oldIFS=$IFS
IFS=:
...
IFS=$oldIFS
ほとんどの場合、実際には動作しますが、POSIXとの動作は保証されません。
その理由は、$IFS
以前に設定されていない場合、これは次のことを意味するためです。基本的な分割動作(つまり、POSIXシェルでは空白、タブ、または改行に分割されます)、これらのコマンドの後に最終的に空の状態に設定されます(つまり、分裂なし)。
もう1つの潜在的な問題は、メソッドを一般化し、さまざまな関数でそれを使用している場合は、上記のセクションで同じことを行う関数...
($IFS
inのコピーを作成$oldIFS
)を呼び出す場合は、元の$oldIFS
エラーを復元してください$IFS
。
代わりに、可能であればサブシェルを使用できます。
(
IFS=:
...
)
# only the subshell's IFS was affected, the parent still has its own IFS
私のアプローチは、$ IFSを設定してオンまたはオフにset -o noglob
することです。毎回単語分割(まれな場合)が必要であり、以前の値を復元することには気にしません。もちろん、スクリプトがこの慣行に従わず、基本的な単語分離動作を採用している他の人のコードを呼び出す場合は機能しません。
答え2
必要に応じて設定し、IFS
シェルに単語分割を実行させます。
IFS=':'
for dir in $PATH; do
[ -x "$dir"/"$1" ] && echo $dir
done
bash
これは、dash
およびで動作しますksh
が、最新バージョンでのみテストされました。
答え3
固定数のフィールドを変数として読み取る必要がある場合は、次の方法を使用できます。
input="age:30"
IFS=':' read -r first_field second_field <<< "$input"
echo "$first_field"
echo "$second_field"
私はそれを見つけたグレッグのウィキ。
バックスラッシュを特別に扱うべきではないことを伝えます-r
。read