ファイル名にスペースを含む単一のファイルを移動すると、次のように動作します。
$ mv "file with spaces.txt" "new_place/file with spaces.txt"
これで、スペースを含めることができるファイルのリストがあり、それを移動したいと思います。たとえば、
$ echo "file with spaces.txt" > file_list.txt
$ for file in $(cat file_list.txt); do mv "$file" "new_place/$file"; done;
mv: cannot stat 'file': No such file or directory
mv: cannot stat 'with': No such file or directory
mv: cannot stat 'spaces.txt': No such file or directory
最初の例はうまくいきますが、2番目の例はうまくいきません。どのように動作させることができますか?
答え1
いつも、いつも使ってくださいfor foo in $(cat bar)
。これは一般的に知られているエラーです。バッシュトラップ番号1。以下を使用する必要があります。
while IFS= read -r file; do mv -- "$file" "new_place/$file"; done < file_list.txt
for
ループを実行すると、bashは読み取る内容にトークン化を適用します。つまり、、、および次a strange blue cloud
に読み込まれます。a
strange
blue
cloud
$ cat files
a strange blue cloud.txt
$ for file in $(cat files); do echo "$file"; done
a
strange
blue
cloud.txt
比較:
$ while IFS= read -r file; do echo "$file"; done < files
a strange blue cloud.txt
それとも頑張るならウルク:
$ cat files | while IFS= read -r file; do echo "$file"; done
a strange blue cloud.txt
したがって、while
ループは入力を読み取り、をread
使用して各行を変数に割り当てます。IFS=
入力フィールド区切り文字をNULL *に設定すると、この-r
オプションはread
バックスラッシュエスケープを解釈しないようにします(タブの代わりに\t
スラッシュ+として扱われます)。t
afterは、「--の後のすべてをオプションではなく引数として扱う」ことを--
意味します。これにより、正しいファイル名で始まるファイル名を適切に処理できます。mv
-
* 厳密に言えば、ここでは必要ありません。この場合の唯一の利点は、read
先行または末尾のスペースを削除しないことです。ただし、改行文字を含むファイル名を処理する必要があるか、通常は任意のファイル名を処理できます。
答え2
$(cat file_list.txt)
POSIXシェルのリストコンテキストのように引用符がないのは、分割bash
+グローブ演算子ですzsh
(のみ分けるセクションで期待されるように)。
($IFS
基本的にプログラム制御、TAB、NL)とglobbingを完全にオフにしないとglobbingが発生します。
ここでは改行文字だけに分割し、グローバル部分は必要ないので、次のようにする必要があります。
IFS='
' # split on newline only
set -o noglob # disable globbing
for file in $(cat file_list.txt); do # split+glob
mv -- "$file" "new_place/$file"
done
while read
mv
これには、空の行を削除し、終了していない末尾の行を維持し、stdinを維持する(たとえば、メッセージが表示される場合は必須)という利点もあります(ループとは反対)。
ファイルの内容全体をメモリに保存する必要があるという欠点があります(bash
と同じシェルを複数回使用zsh
)。
一部のシェルでは(ksh
そしてzsh
それほど少ないですがbash
)$(<file_list.txt)
代わりに使用して最適化できます$(cat file_list.txt)
。
loop を使用して同じことを行うには、while read
次のものが必要です。
while IFS= read <&3 -r file || [ -n "$file" ]; do
{
[ -n "$file" ] || mv -- "$file" "new_place/$file"
} 3<&-
done 3< file_list.txt
または以下を使用してbash
:
readarray -t files < file_list.txt &&
for file in "${files[@]}"
[ -n "$file" ] || mv -- "$file" "new_place/$file"
done
または以下を使用してzsh
:
for file in ${(f)"$(<file_list.txt)"}
mv -- "$file" "new_place/$file"
done
またはGNUを使用mv
して、次のようにしますzsh
。
mv -t -- new_place ${(f)"$(<file_list.txt)"}
または、ksh/zsh/bash で GNUmv
と GNU を使用します。xargs
xargs -rd '\n' -a <(grep . file_list.txt) mv -t -- new_place
引用しない拡張の意味の詳細については、次を参照してください。bash / POSIXシェルで変数を引用することを忘れてしまうセキュリティリスク
答え3
スクリプトの代わりに find.. を使用できます。
find -type f -iname \*.txt -print0 | xargs -IF -0 mv F /folder/to/destination/
ファイルがファイルにある場合は、次のことができます。
cat file_with_spaces.txt | xargs -IF -0 mv F /folder/to/destination
しかし、2番目はよくわかりません...
幸運を祈る