シェルプログラミングで文字列を分割する安全で移植可能な方法は何ですか?

シェルプログラミングで文字列を分割する安全で移植可能な方法は何ですか?

シェルスクリプトを作成するときに文字列を分割したい場合がよくあります。以下は非常に簡単な例です。

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/sbinPATH/*/*/*/../../../*/*/*/*/../../../*/*/*/*

$PATHのコンポーネントは現在のディレクトリ(.)を表し、この場合は正しくありません/$dir/$1この場合、回避策はを使用するとき($dir${dir:+/}$1を書き込むか変更することです)。$dir.printf '%s\n' "$dir"

//foo必ずしもと同じである必要はないので にあれば/fooにある必要はなく、これです。したがって、末尾のスラッシュを削除します。/$PATH$dir/$1//$1${dir%/}

その後、いくつかの他の質問があります。

の場合はフィールドです$PATH":"仕切りそしての場合は$IFSフィールドです。ターミネーター(はい、わかりましたSS分割ツール, 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つの潜在的な問題は、メソッドを一般化し、さまざまな関数でそれを使用している場合は、上記のセクションで同じことを行う関数...$IFSinのコピーを作成$oldIFS)を呼び出す場合は、元の$oldIFSエラーを復元してください$IFS

代わりに、可能であればサブシェルを使用できます。

(
  IFS=:
  ...
)
# only the subshell's IFS was affected, the parent still has its own IFS

私のアプローチは、$ IFSを設定してオンまたはオフにset -o noglobすることです。毎回単語分割(まれな場合)が必要であり、以前の値を復元することには気にしません。もちろん、スクリプトがこの慣行に従わず、基本的な単語分離動作を採用している他の人のコードを呼び出す場合は機能しません。

おすすめ記事