Bash関数引数 - ファイル名のスペース

Bash関数引数 - ファイル名のスペース

参照パラメータに関する規則があることはわかっていますが、どんなに努力してもそれをどのように機能させるかわからないようです。

単一のファイル名を引数として受け入れ、そのファイル名を使用してコマンドラインを作成し、コマンドラインを実行する関数があります。ファイルにスペースが含まれていない場合は機能させることができますが、スペースを含むファイル名を導入すると操作が中断されます。空白を使用して操作しようとしたすべてが機能しないか、空白のないファイルでも機能しなくなりました。

以下は空白のないサンプルスクリプトです。

#!/bin/bash
list_files() {
    file="${1}"
    cmd="cat ${file}"
    echo "Running command: '${cmd}'"
    ${cmd}
}

TARGET_DIR="${1}"

while IFS= read -d $'\0' -r file; do
    list_files "${file}"
done < <(find ${TARET_DIR} -type f -print 0)

次の構造のディレクトリを作成しました。

./files/
|-- one                # ==> Contains the text "files/one"
|-- two                # ==> Contains the text "files/two"
|-- three and four     # ==> Contains the text "files/three and four"
|-- five               # ==> Contains the text "files/five"

上記のディレクトリで上記のスクリプトを実行すると、次の結果が表示されます。

bash-3.2$ ./test.bash ./files
Running Command: 'cat ./files/one'
files/one
Running Command: 'cat ./files/two'
files/two
Running Command: 'cat ./files/three and four'
cat: ./files/three: No such file or directory
cat: and: No such file or directory
cat: four: No such file or directory
Running Command: 'cat ./files/five'
files/five

私は何が間違っていましたか?ファイル名に関係なく、このようなスクリプトが機能するようにするにはどうすればよいですか?

注:この例では簡単です。まず、コマンドを変数に保存せずに印刷し、コマンド自体を2回入力して呼び出します。しかし、実際のスクリプトでは、コマンドははるかに複雑です。後で作成する必要がある場合は、1か所に保管してください。

ベストアンサー1

変える:

cmd="cat ${file}"
printf '%s\n' "Running command: '${cmd}'"
${cmd}

evalシェルコードとして解釈されるように固定されたコマンドを渡します。

cmd='cat -- "$file"'
printf '%s\n' "Running command: '${cmd}'"
eval "$cmd"

Running command: cat -- "$file"おそらく希望の結果は出力されません。

または(具体的に)拡張が次に渡されるようにbash正しいprintf %q引用符を使用してください。$fileeval

printf -v cmd 'cat -- %q' "$file"
printf '%s\n' "Running command: $cmd"
eval "$cmd"

あるいは、ここに単純なコマンドがあるので、その単純なコマンドのパラメータを配列に保存することもできます。

cmd=(cat -- "$file")
printf 'Running command: '
printf ' %q' "${cmd[@]}"
printf '\n'
"${cmd[@]}"

$file同様の文字が含まれていないことが保証されている特定の文字を知っている場合::この文字はUnixのファイル名で完全に有効な文字であることに注意してください)、次のことができます。

cmd="cat:--:$file"
IFS=:  # split on :
set -f # disable glob
# use the split+glob operator by leaving the variable unquoted:
printf 'Running command: '
printf ' %q' $cmd
printf '\n'
$cmd

この特定のケースでは、次のこともできます。

(PS4='Running command: '
set -x; cat -- "$file")

(メッセージはstderrに送信されます。)

でも:

PS4='Running command: ' find "$TARGET_DIR" -type f -exec sh -xc '
  cat "$1"' sh {} \;

shまたは、あまりにも多くのコマンドを実行したくない場合は、/opt/ast/bin/catにマップされた組み込み関数がある場所をcat使用し、構文を使用してできるだけ少ない数のシェルを実行してください。ksh93cat{} +

PS4='Running command: ' find "$TARGET_DIR" -type f -exec ksh93 -c '
  PATH=/opt/ast/bin; set -x; for file do cat "$file"; done' ksh93 {} +

ヘッダーと共にファイルの内容を表示し、できるだけ少ない数のコマンドを実行するには、GNUシステムで次のものを使用することもできますhead

$ find "$TARGET_DIR" -type f -exec head -vc-0 {} +
==> ./file 1 <==
1
2

==> ./file 2 <==
test

そしてzsh

zmodload zsh/mapfile
for file (**/*(D.)) printf '==> %q <==\n%s\n' $file $mapfile[$file]

おすすめ記事