シェルスクリプト内から複数のファイルをディレクトリにコピーしようとしています。これらのファイルには、空白、括弧など、さまざまな「見苦しい」文字が含まれています。ただし、これらの文字をエスケープするときに行き詰まりbash
、cp
非常に奇妙な方法で処理されているようです。
シナリオは次のとおりです:
シェル内からこのコマンドを発行すると、うまく機能します。
cp /somedir/a\ file.png /somedir/another\ file.png /someotherdir/
しかし、文字列からコピーするファイルを読み込むと、突然おかしくなります 。
var="/somedir/a\ file.png /somedir/another\ file.png"
cp "$var" /someotherdir/cp: cannot stat 'a\\ file.png another\\ file.png': No such file or directory
これは、変数を文字列として指定し、複数のファイルであるにもかかわらず 1 つのファイルであると認識しているためだと思いますcp
。変数を引用符 ( var="/somedir/a\ file.png /somedir/another\ file.png"; cp $var /someotherdir/
) で囲まずに同じコマンドを発行すると、さらに奇妙なエラーが発生します。
cp: cannot stat 'a\\ file.png another\\ file.png': No such file or directory
cp: cannot stat 'file.png': No such file or directory
cp: cannot stat 'another\\': No such file or directory
cp: cannot stat 'file.png': No such file or directory
私の脱出を完全に無視しているようです。何が間違っているのでしょうか?
編集:// どうやらファイルのリストをコピーには答えがありますxargs
が、なぜ bash がここで奇妙な動作をするのかまだ疑問に思っています。
答え1
先生、これをやると痛いんです…
だったらそんなことしないで!
真面目な話、スペースを含むファイル名のスペース区切りリストを 1 つの変数に割り当てようとしているのはなぜですか? それは災難のもとです。
あなたの暗黙の質問に答えると、あなたが使用したというコマンドに対して、あなたが示すエラー メッセージが意味をなさないことに同意します。
どのファイルをコピーするかをどのように決定するかが述べられていないため、どの解決策が有効で、どの解決策が有効でないかを知ることは困難です。考えられるアプローチは次のとおりです。
︙ f1="/somedir/a ファイル.png" ︙ f2="/somedir/別のファイル.png" ︙ cp "$f1" "$f2" "$f3" … /someotherdir
/
(の末尾に は必要ありません/someotherdir/
が、あっても問題ありません。) これは、コピーする必要があるファイルの最大数を事前に知っておく必要があり、f
各ファイルをどの変数に割り当てるかを判断する方法が必要であるという事実によって制約されます。
これはあなたが今やっていることに近いです:
var="/somedir/a\ file.png /somedir/another\ file.png …" echo "$var" | 読み取り中に f1 f2 f3 … する cp "$f1" "$f2" "$f3" … /someotherdir 終わり
このread
コマンドは、$var
文字列を通常のスペースで分割し、バックスラッシュを削除して(スペース)\
のみを残します。ただし、コピーする必要があるファイルの最大数を事前に知っておく必要があります。(このループは 1 回しか繰り返されませんが、ループ構造が
必要です。)while
おそらくこれは近いでしょう:
ファイルリスト=/tmp/my_filelist.$$ ︙ echo "/somedir/a file.png" >> "$filelist" ︙ echo "/somedir/another file.png" >> "$filelist" ︙ ファイル名の読み取り中 する cp "$filename" /someotherdir 完了 < "$filelist"
もう 1 つのバリエーションは、変数配列を使用することです。その方法については、bash
man ページで確認できます。
答え2
bash がコマンド ラインを解析するとき、まず引用符、エスケープなどを解釈し、次に変数を置き換えます。変数値にエスケープや引用符などが含まれている場合、コマンドに含まれるときには意図した効果を得るには遅すぎるため、それらは有効になりません。
一般的に、この種の問題を処理する最善の方法は、ファイル名を文字列変数ではなく配列 (ファイル名ごとに 1 つの要素) に格納し、"${array[@]}"
各要素を個別の「単語」として扱うように配列を拡張することです。
files=(/somedir/a\ file.png /somedir/another\ file.png)
cp "${files[@]}" /someotherdir/
更新: ファイル リストを動的に構築する必要がある場合は、配列を使用すると簡単です。
files=() # Start with an empty list
for newfile in somethingorother; do
files+=("$newfile")
done
答え3
bash を使用している場合は、エスケープ文字の使用を避けるためにファイル名を二重引用符で囲むことができます。
cp /old_location/This\ is\ an\ \[ugly\] \file.tmp /new_location/This\ is\ an\ \[ugly\] \file.tmp
#!/bin/bash
シェルスクリプトで使用する場合は、代わりにこれを使用します。
cp "/old_location/This is an [ugly] file with extras $$$.tmp" "/new_location/Ugly file with extras $$$.tmp"
あなたの場合、変数内の値を使用していますが、変数を間違って渡しています。変数にスペースが含まれている場合は、二重引用符で囲む必要があります。
var="/somedir/a\ file.png /somedir/another\ file.png"; cp "$var" /someotherdir/
二重引用符で囲まないと、スペースで区切られた複数の変数を使用するのと同じになります。