bash: 空白のあるファイルの移動

bash: 空白のあるファイルの移動

ファイル名にスペースを含む単一のファイルを移動すると、次のように動作します。

$ 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に読み込まれます。astrangebluecloud

$ 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スラッシュ+として扱われます)。tafterは、「--の後のすべてをオプションではなく引数として扱う」ことを--意味します。これにより、正しいファイル名で始まるファイル名を適切に処理できます。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 readmvこれには、空の行を削除し、終了していない末尾の行を維持し、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番目はよくわかりません...

幸運を祈る

関連情報