
2 つのテキスト ファイルsrc.txt
とがあるとしますdest.txt
。ここにはsrc.txt
ファイル名のリスト (一部にはスペースが含まれます) が含まれ/src/dir/
、dest.txt
にはそれらが属する完全なファイル パスのリスト (スペースを含む) がランダムな順序で含まれます。例:
ソース:
file 1.jpg
file_2.html
file 3.jpg
宛先.txt:
/dest/dir 1/file 3.jpg
/dest/file4.txt
/dest/file 5.txt
/dest/dir 2/file 1.jpg
/dest/file_2.html
このバッチ移動操作をシェルから実行するにはどうすればよいですか?while read
ソース ファイルのループを使用して作業しており、 コマンドを使用する必要があることはほぼ確実ですが、ここでまたは が必要mv
かどうかはわかりません。および スペース文字解決エラーが発生し続けます。grep
sed
cannot stat...
答え1
とzsh
:
src=(${(f)"$(<src.txt)"})
for f (${(f)"$(<dest.txt)"})
(($src[(Ie)$f:t])) && mv /src/dir/$f:t $f
これは配列内の各ファイルを読み取り、配列内の各要素に対して「目的地」配列、ベース名(先頭のパス名要素をすべて削除する:t
修飾子zsh
)も「ソース」配列の場合は、ファイルを移動します。 ドライランを実行するには、mv
を に置き換えますprintf '"%s" -> "%s"\n'
。
ここで、(まだzsh
) で以下を実行することもできます:
for f (${(f)"$(grep -Ff src.txt dest.txt)"})
mv /src/dir/$f:t $f
src.txt
これは、 のファイル名がのパスのリストにあるディレクトリ名(またはその名前の一部)のいずれとも一致しない限り、正常に動作しますdest.txt
(たとえば、 のファイル名data1
とのsrc.txt
ようなパスは、誤検知を引き起こします)。これを回避するには、のパスの最後のコンポーネントのみに一致するように、固定文字列ではなくパターンとして にファイル名を渡すことができます(つまり、 のような正規表現を使用します)。ただし、そのためには、 のファイル名にあるすべての特殊文字(ある場合)をエスケープする必要があります。たとえば、この場合は( ) を使用します。/path/data1_dir/some_file
dest.txt
grep
/filename$
F
dest.txt
src.txt
bash
4
readarray -t files < <(sed 's|[[\.*^$/]|\\&|g;s|.*|/&$|' src.txt | grep -f- dest.txt)
for f in "${files[@]}"; do mv /src/dir/"${f##*/}" "$f"; done
答え2
改行が許容される区切り文字である場合、次のコードは POSIX シェルでかなり堅牢になるはずです。
IFS='
';set -f
for f in $(cat <"$destfile")
do [ -e "./${f##*/}" ] ||
[ -h "./${f##*/}" ] &&
mv "./${f##*/}" "$f"
done
この解決策には、考えられる問題が 2 つあります。
入力ファイルのサイズが大きすぎるため、一度に分割することはできません。
- 私のシステムでは、入力が数万行に近づくまで、これを真剣に検討する必要はありません。
ファイル名は
$destfile
現在のディレクトリに存在する可能性があるが、ないとにかく動かされる。- このソリューションでは、2 つの入力ファイルの比較を完全に省略し、現在のディレクトリに存在する最後のパス名コンポーネントのみをチェックするため
$destfile
、ファイル名が意図せず一致する可能性がある場合は考慮する必要はありません。
- このソリューションでは、2 つの入力ファイルの比較を完全に省略し、現在のディレクトリに存在する最後のパス名コンポーネントのみをチェックするため
最初の問題のみを処理する必要がある場合:
sed -ne"s|'|'"'\\&&|g' <"$destfile" \
-e "s|.*/\([^/].*\)|_mv './\1' '&'|p" |
sh -c '_mv(){ [ -e "$1" ]||[ -h "$1" ]&& mv "$@";};. /dev/fd/0'
sh
の場合は、末尾のdash
を削除して次のように使用できます。. /dev/fd/0
sed ... | sh -cs '_mv(){ ...;}'
... は、dash
奇妙なことに、コマンドラインと標準入力の呼び出しオプションの両方を、文句を言わずに同時に処理します。これは移植性が高くありませんが、. /dev/fd/0
移植性は高いものの、厳密には標準に準拠していません。
2 番目の問題が懸念される場合:
export LC_ALL=C
sed -ne'\|/$|!s|.*/\(.*\)|\1/&|p' <"$destfile" |
sort -t/ -k1,1 - ./"$srcfile" | cut -d/ -f2- |
sed -e "\|/|!N;\|\n.*/|!d" \
-e "s|'|'"'\\&&|g' \
-e "s|\n|' '|;s|.*|mv './&'|" | sh
./"$srcfile"
... 内のすべてのファイル名が 内のパスの末尾に適切かつ同一に配置されている限り、これは非常にうまく処理されるはずです"$destfile"
。sort
は、それ以外は同一の 2 つの比較のうち短い方を常に先頭に表示します。したがって、最初のフィールドのみが重要で、ファイル名が からの各パス名の先頭に追加されている場合、両方のファイルの"$destfile"
マージsort
操作によって次のようなシーケンスが出力されます。
$srcfile: no /
$destfile: match
$destfile: unique
$destfile: unique
...
$srcfile: no /
$destfile: match
$destfile: unique
...したがって、一致しない行で始まる行のペアのみに注意する必要があります/
。
答え3
while read i; do echo cp \""$i"\" \"$(grep "/$i$" dst.txt)\"; done < src.txt
これにより、実行された内容が印刷されます。 を取り除いてecho
、実際にファイルをコピーします。
答え4
ワンライナー スクリプトはスクリプトを生成し、スクリプトはスクリプトを生成します。
sed
この例では、 onの最初の呼び出しを使用して、で実行されるsrc.txt
2 番目のスクリプトを生成し、ファイルをコピーするシェル スクリプトを生成します。sed
dest.txt
ワンライナーは次のとおりです。
$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt #| sh -x
出力は次のようになります。
cp "file 3.jpg" "/dest/dir 1/file 3.jpg";
cp "file 1.jpg" "/dest/dir 2/file 1.jpg";
cp "file_2.html" "/dest/file_2.html";
#| sh
コマンドの末尾のコメントに注意してください。こうすることで、コマンドを試して何が行われるかを確認し、問題がなければパイプのコメントを解除しsh
て実際にファイルをコピーすることができます。
内部の sed コマンドは、src.txt から sed スクリプトを構築します。生成されたスクリプトの最初の行は次のようになります。
/\/file 1.jpg$/ { s/^/cp file 1.jpg /; p; }
仕組みは以下のとおりです:
入力:
$ cat src.txt
file 1.jpg
file_2.html
file 3.jpg
$ cat dest.txt
/dest/dir 1/file 3.jpg
/dest/file4.txt
/dest/file 5.txt
/dest/dir 2/file 1.jpg
/dest/file_2.html
最初のsed
呼び出し。これは、2 回目の呼び出しによって解釈される生成されたスクリプトを示していますsed
。
$ sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt
/\/file 1.jpg$/ { s/^/cp "file 1.jpg" "/; s/$/";/; p; }
/\/file_2.html$/ { s/^/cp "file_2.html" "/; s/$/";/; p; }
/\/file 3.jpg$/ { s/^/cp "file 3.jpg" "/; s/$/";/; p; }
シェル コマンド置換を使用して、最初のコマンドの出力を、sed
の 2 回目の呼び出しに渡されるコマンド ラインのスクリプトとして使用しますsed
。
$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt
cp "file 3.jpg" "/dest/dir 1/file 3.jpg";
cp "file 1.jpg" "/dest/dir 2/file 1.jpg";
cp "file_2.html" "/dest/file_2.html";
ここで、xtrace オプション ( ) を使用して、sed の出力をシェルにパイプしますsh -x
。ファイルがないため、エラーが発生します。
$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt | sh -x
+ cp file 3.jpg /dest/dir 1/file 3.jpg
cp: cannot stat ‘file 3.jpg’: No such file or directory
+ cp file 1.jpg /dest/dir 2/file 1.jpg
cp: cannot stat ‘file 1.jpg’: No such file or directory
+ cp file_2.html /dest/file_2.html
cp: cannot stat ‘file_2.html’: No such file or directory