Kopieren Sie mehrere Dateien per cp von einem String

1

Ich versuche, mehrere Dateien aus einem Shellscript in ein Verzeichnis zu kopieren. Diese Dateien enthalten alle Arten von "hässlichen" Zeichen, wie Leerzeichen, Klammern und was sonst nicht. Allerdings stecke ich fest, wenn es darum geht, diesen als zu entkommen bash und cp scheinen sehr seltsam damit umzugehen.

Hier ist das Szenario:
Wenn ich diesen Befehl in meiner Shell absetze, funktioniert das wie ein Zauber:
cp /somedir/a\ file.png /somedir/another\ file.png /someotherdir/

Beim Lesen der zu kopierenden Dateien aus einem String wird es jedoch plötzlich seltsam:
var="/somedir/a\ file.png /somedir/another\ file.png"
cp "$ var" / someotherdir /

Ergebnisse in cp: cannot stat 'a\\ file.png another\\ file.png': No such file or directory

Ich glaube, das liegt daran, dass ich die Variable als String gebe und cp glaubt, es ist eine Datei, obwohl es mehrere Dateien ist. Wenn Sie denselben Befehl ausführen, ohne die Variable in Anführungszeichen zu setzen ( var="/somedir/a\ file.png /somedir/another\ file.png"; cp $var /someotherdir/ ) Ich bekomme einen noch seltsameren Fehler:
cp: cannot stat 'a\\ file.png another\\ file.png': No such file or directory
cp: cannot stat 'file.png': No such file or directory
cp: cannot stat 'another\\': No such file or directory
cp: cannot stat 'file.png': No such file or directory

Es scheint meine Flucht völlig zu ignorieren. Was mache ich falsch?

BEARBEITEN :// Wie es scheint Liste der Dateien kopieren hat eine Antwort mit xargs, aber ich frage mich immer noch, warum bash sich hier so komisch verhält.

David Stockinger
quelle

Antworten:

2

Doktor, es tut weh, wenn ich das tue ...

Dann mach das nicht!

Im Ernst, warum versuchen Sie einer einzelnen Variablen eine durch Leerzeichen getrennte Liste von Dateinamen zuzuweisen, die Leerzeichen enthalten? Das ist ein Rezept für eine Katastrophe.

Um Ihre implizite Frage zu beantworten: Ich stimme den von Ihnen angezeigten Fehlermeldungen zu machen für die Befehle, die Sie verwendet haben, keinen Sinn.

Da Sie nicht angegeben haben, wie Sie bestimmen, welche Dateien kopiert werden sollen, ist es schwer zu wissen Welche Lösung wird für Sie funktionieren und welche nicht? Hier ist ein möglicher Ansatz:

        ︙
f1="/somedir/a file.png"
        ︙
f2="/somedir/another file.png"
        ︙
cp "$f1" "$f2" "$f3" … /someotherdir

(Sie brauchen das nicht / am Ende von /someotherdir/, aber es tut auch nicht weh.) Dies wird durch die Tatsachen eingeschränkt, die Sie müssen im Voraus wissen, wie viele Dateien Sie maximal kopieren müssen. und Sie brauchen eine Möglichkeit, um herauszufinden, was f Variable, der jeder zugewiesen werden soll.

Dies entspricht in etwa dem, was Sie gerade tun:

var="/somedir/a\ file.png /somedir/another\ file.png …"
echo "$var" | while read f1 f2 f3 …
do
    cp "$f1" "$f2" "$f3" … /someotherdir
done

Das read Befehl wird das brechen $var auf den ebenen Flächen auseinander ziehen, und entfernen Sie die Backslashes als gerade   (Platz). Sie müssen jedoch im Voraus wissen, wie viele Dateien Sie maximal kopieren müssen. (Du brauchst das while Schleifenkonstruktion, obwohl diese Schleife nur einmal wiederholt wird.)

Vielleicht nähert sich dieser:

filelist=/tmp/my_filelist.$$
        ︙
echo "/somedir/a file.png" >> "$filelist"
        ︙
echo "/somedir/another file.png" >> "$filelist"
        ︙
while read filename
do
    cp "$filename" /someotherdir
done < "$filelist"

Eine andere Variante ist die Verwendung eines variablen Arrays. Wie das geht, erfahren Sie im bash Manpage.

Scott
quelle
Die Anzahl der zu kopierenden Dateien ist variabel und beim Programmstart unbekannt. Ich habe meinen Code geändert, um die Liste durch neue Zeilen zu trennen, und jetzt analysiere ich sie mit xargs. Funktioniert auch gut
David Stockinger
3

Wenn bash eine Befehlszeile analysiert, werden zuerst Anführungszeichen, Escapezeichen usw. interpretiert und anschließend Variablen ersetzt. Wenn die Variablenwerte Escapezeichen, Anführungszeichen usw. enthalten, werden sie nie wirksam, da es zu spät ist, bis sie in den Befehl aufgenommen werden, um den beabsichtigten Effekt zu erzielen.

Im Allgemeinen besteht die beste Möglichkeit zur Behebung dieses Problems darin, die Dateinamen in einem Array (ein Element pro Dateiname) und nicht in einer Zeichenfolgenvariablen zu speichern. dann benutze "${array[@]}" So erweitern Sie das Array, wobei jedes Element als separates "Wort" behandelt wird:

files=(/somedir/a\ file.png /somedir/another\ file.png)
cp "${files[@]}" /someotherdir/

Update: Wenn Sie die Dateiliste dynamisch erstellen müssen, ist das mit einem Array ganz einfach:

files=() # Start with an empty list
for newfile in somethingorother; do
    files+=("$newfile")
done
Gordon Davisson
quelle
1

Wenn Sie bash verwenden, können Sie den Namen der Datei in doppelte Anführungszeichen setzen, um die Verwendung von Escapezeichen zu vermeiden:

cp /old_location/This\ is\ an\ \[ugly\] \file.tmp /new_location/This\ is\ an\ \[ugly\] \file.tmp

Verwenden Sie dies stattdessen, wenn Sie verwenden #!/bin/bash In Ihrem Shell-Skript:

cp "/old_location/This is an [ugly] file with extras $$$.tmp" "/new_location/Ugly file with extras $$$.tmp"

In Ihrem Fall verwenden Sie die Werte in einer Variablen, aber Sie übergeben die Variable falsch. Wenn eine Variable Leerzeichen enthält, müssen Sie sie in Anführungszeichen setzen:

var="/somedir/a\ file.png /somedir/another\ file.png"; cp "$var" /someotherdir/

Wenn Sie es nicht in Anführungszeichen setzen, werden mehrere durch Leerzeichen getrennte Variablen verwendet.

Peter
quelle
Das Problem ist, dass das nicht mit mehreren Dateien funktioniert
David Stockinger
Ich habe die Antwort mit Ihrer Lösung aktualisiert "$ var" : P
Peter