ファイルのリストを宛先インデックスに一致させて移動する

ファイルのリストを宛先インデックスに一致させて移動する

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かどうかはわかりません。および スペース文字解決エラーが発生し続けます。grepsedcannot 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_filedest.txtgrep/filename$Fdest.txtsrc.txtbash4

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、ファイル名が意図せず一致する可能性がある場合は考慮する必要はありません。

最初の問題のみを処理する必要がある場合:

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.txt2 番目のスクリプトを生成し、ファイルをコピーするシェル スクリプトを生成します。seddest.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

関連情報