Verschieben einer Dateiliste durch Abgleichen mit einem Zielindex

Verschieben einer Dateiliste durch Abgleichen mit einem Zielindex

Angenommen, ich habe zwei Textdateien src.txtund dest.txt, wobei src.txtin eine Liste von Dateinamen (einige davon mit Leerzeichen) /src/dir/und dest.txtin zufälliger Reihenfolge eine Liste der vollständigen Dateipfade (wiederum mit Leerzeichen) enthalten ist, zu denen sie gehören. Beispiel:

quelle.txt:

file 1.jpg
file_2.html
file 3.jpg

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

Wie kann ich diesen Batch-Verschiebevorgang von der Shell aus durchführen? Ich habe mit einer while readSchleife über die Quelldatei gearbeitet und bin ziemlich sicher, dass ich den mvBefehl verwenden muss, aber ich bin mir nicht sicher, ob greper sedhier benötigt wird. Ich stoße immer wieder auf cannot stat...Fehler bei der Auflösung von Leerzeichen.

Antwort1

Mit zsh:

src=(${(f)"$(<src.txt)"})
for f (${(f)"$(<dest.txt)"})
(($src[(Ie)$f:t])) && mv /src/dir/$f:t $f

Dadurch wird jede Datei in einem Array gelesen und dann für jedes Element in der"Ziel"Array, wenn der Basisname ( :tist ein zshModifikator, der alle führenden Pfadnamenkomponenten entfernt) auch im"Quelle"Array, dann verschiebt es die Datei. Um einen Probelauf durchzuführen, ersetzen Sie mvdurch printf '"%s" -> "%s"\n'.


Sie können nun auch Folgendes ausführen (immer noch in zsh):

for f (${(f)"$(grep -Ff src.txt dest.txt)"})
mv /src/dir/$f:t $f

was gut funktioniert, solange keiner der Dateinamen in mit src.txteinem der Verzeichnisnamen (oder Teilen dieser Namen) in der Liste der Pfade in übereinstimmt dest.txt(z. B. würde ein Dateiname data1in src.txtund ein Pfad wie /path/data1_dir/some_filein dest.txtein falsches Positiv ergeben). Um das zu vermeiden, können Sie die Dateinamen als grepMuster (d. h. mit regulären Ausdrücken wie /filename$) statt als Ffeste Zeichenfolgen übergeben, um nur die letzte Komponente der Pfade in abzugleichen dest.txt. Dies erfordert allerdings das Maskieren aller Sonderzeichen (falls vorhanden) in Dateinamen in src.txt, z. B. dieses Mal mit 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

Antwort2

Wenn ein Zeilenumbruch ein akzeptables Trennzeichen ist, sollte das Folgende in einer POSIX-Shell ziemlich robust sein:

IFS='
';set -f
for   f in $(cat <"$destfile")
do    [ -e "./${f##*/}" ] ||
      [ -h "./${f##*/}" ] &&
      mv   "./${f##*/}"  "$f"
done

Ich kann mir zwei mögliche Probleme mit dieser Lösung vorstellen:

  • Die Eingabedatei ist einfach zu groß, um sie auf diese Weise auf einmal aufzuteilen.

    • Auf meinem System ist dies erst dann wirklich eine ernsthafte Überlegung wert, wenn die Eingabe viele Zehntausend Zeilen umfasst.
  • Ein Dateiname in $destfilekönnte im aktuellen Verzeichnis vorhanden sein und sollte dennochnichttrotzdem verschoben werden.

    • Da bei dieser Lösung auf den Vergleich der beiden Eingabedateien vollständig verzichtet wird und nur die $destfileExistenz der letzten Pfadnamenkomponente im aktuellen Verzeichnis geprüft wird, sollte eine unbeabsichtigte Übereinstimmung der Dateinamen nicht berücksichtigt werden.

Wenn nur das erste Problem behoben werden muss:

sed -ne"s|'|'"'\\&&|g' <"$destfile"    \
    -e "s|.*/\([^/].*\)|_mv './\1' '&'|p" | 
sh  -c '_mv(){ [ -e "$1" ]||[ -h "$1" ]&& mv "$@";};. /dev/fd/0'

Wenn dies bei Ihnen der shFall ist, dashkönnen Sie das . /dev/fd/0am Ende weglassen und Folgendes verwenden:

sed ... | sh -cs '_mv(){ ...;}'

...weil dashseltsamerweise sowohl die Befehlszeilen- als auch die Standardeingabeoptionen gleichzeitig und ohne Beanstandungen verarbeitet werden. Das wäre nicht sehr portabel, aber . /dev/fd/0– obwohl es ziemlich portabel ist – ist es auch nicht streng standardkonform.

Wenn das zweite Problem Anlass zur Sorge gibt:

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

... das sollte damit sehr gut zurechtkommen, solange alle Dateinamen in ./"$srcfile"richtig und identisch am Ende eines Pfades in berücksichtigt werden "$destfile". sortwird immer den kürzeren von zwei ansonsten identischen Vergleichen nach oben stellen. Wenn also nur das erste Feld zählt und der Dateiname vor den Anfang jedes Pfadnamens von gestellt wird, wird "$destfile"eine Zusammenführung sortbeider Dateien Sequenzen wie die folgenden ausgeben:

$srcfile:  no /
$destfile: match
$destfile: unique
$destfile: unique
...
$srcfile:  no /
$destfile: match
$destfile: unique

...und Sie müssen sich daher nur mit den Zeilenpaaren befassen, die mit einer nicht übereinstimmenden Zeile beginnen /.

Antwort3

while read i; do echo cp \""$i"\" \"$(grep "/$i$" dst.txt)\"; done < src.txt

Dadurch wird ausgedruckt, was getan worden wäre. Entfernen Sie einfach das, echoum die Dateien tatsächlich zu kopieren.

Antwort4

Ein einzeiliges Skript generiert ein Skript, das ein Skript generiert.

In diesem Beispiel verwenden wir einen ersten Aufruf von sedon, src.txtum ein zweites Skript zu generieren sed, das auf ausgeführt wird dest.txt, um ein Shell-Skript zum Kopieren der Dateien zu generieren.

Hier ist der Einzeiler:

$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt #| sh -x

und die Ausgabe:

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";

Beachten Sie den Kommentar #| sham Ende des Befehls. Auf diese Weise können Sie den Befehl ausprobieren und sehen, was er bewirkt. Wenn alles gut ist, entfernen Sie das Kommentarzeichen aus der Pipe shund kopieren Sie die Dateien wirklich.

Der innere sed-Befehl erstellt ein sed-Skript aus src.txt. Die erste Zeile des generierten Skripts sieht folgendermaßen aus:

/\/file 1.jpg$/ { s/^/cp file 1.jpg /; p; }

So funktioniert es:

Eingang:

    $ 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

Erster sedAufruf. Dies zeigt das generierte Skript, das beim zweiten Aufruf von interpretiert wird 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; }

Verwenden Sie die Shell-Befehlsersetzung, um die Ausgabe des ersten sedBefehls als Skript in der Befehlszeile zu verwenden, das an den zweiten Aufruf von übergeben wird 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";

Leiten Sie nun die Ausgabe von sed mit der Option xtrace ( sh -x) an die Shell weiter. Ich habe keine der Dateien, daher die Fehler:

$ 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

verwandte Informationen