Копировать несколько файлов через cp из строки

Копировать несколько файлов через cp из строки

Я пытаюсь скопировать несколько файлов в каталог из скрипта оболочки. Эти файлы содержат всевозможные "некрасивые" символы, такие как пробелы, скобки и т. д. Однако я застрял, когда дело доходит до экранирования их как, bashи, cpпохоже, справляется с ними очень странно.

Вот сценарий:
при выполнении этой команды из моей оболочки все работает как часы:
cp /somedir/a\ file.png /somedir/another\ file.png /someotherdir/

Однако при чтении файлов для копирования из строки все внезапно становится странным: результаты
var="/somedir/a\ file.png /somedir/another\ file.png"
cp "$var" /someotherdir/

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

Я думаю, это связано с тем, что я задаю переменную как строку и cpсчитает, что это один файл, хотя это несколько файлов. При выполнении той же команды без помещения переменной в кавычки ( var="/somedir/a\ file.png /somedir/another\ file.png"; cp $var /someotherdir/) я получаю еще более странную ошибку:
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

Кажется, он полностью игнорирует мои побеги. Что я делаю не так?

РЕДАКТИРОВАТЬ:// Кажется, чтоКопировать список файловесть ответ с xargs, но я все еще удивляюсь, почему bash ведет себя здесь так странно.

решение1

Доктор, мне больно, когда я это делаю…

Тогда не делайте этого!

Серьёзно, зачем вы пытаетесь присвоить одной переменной список файловых имён, разделённых пробелами, которые содержат пробелы? Это рецепт катастрофы.

Отвечая на ваш подразумеваемый вопрос: я согласен, что сообщения об ошибках, которые вы показываете, не имеют смысла для команд, которые, по вашим словам, вы использовали.

Поскольку вы не сказали, как вы определяете, какие файлы копировать, трудно понять, какое решение подойдет вам, а какое нет. Вот возможный подход:


f1="/somedir/a файл.png"
f2="/somedir/другой файл.png"
cp "$f1" "$f2" "$f3" … /someotherdir

(Вам не нужно указывать the /в конце /someotherdir/, но это тоже не повредит.) Это ограничивается тем фактом, что вам нужно заранее знать максимальное количество файлов, которые вам нужно будет скопировать, и вам нужен какой-то способ выяснить, какой fпеременной присвоить каждый из них.

Это близко к тому, что вы делаете сейчас:

var="/somedir/a\ файл.png /somedir/another\ файл.png …"
echo "$var" | при чтении f1 f2 f3 …
делать
    cp "$f1" "$f2" "$f3" … /someotherdir
сделанный

Команда readразобьет $varстроку на части по простым пробелам и удалит обратные косые черты, оставив только  (пробел). Но вам все равно нужно заранее знать максимальное количество файлов, которые вам нужно будет скопировать. (Вам нужна whileконструкция цикла, хотя этот цикл будет итерироваться только один раз.)

Возможно, этот уже близок к этому:

список_файлов=/tmp/my_filelist.$$
echo "/somedir/a file.png" >> "$filelist"
echo "/somedir/другой файл.png" >> "$filelist"
при чтении имени файла
делать
    cp "$имя_файла" /someotherdir
сделано < "$filelist"

Другой вариант — использовать переменный массив. Вы можете узнать, как это сделать, на bashстранице руководства.

решение2

Когда bash анализирует командную строку, он сначала интерпретирует кавычки, экранирования и т. п., а затем заменяет переменные. Если в значениях переменных есть экранирования, кавычки и т. п., они никогда не вступят в силу, потому что к тому времени, как они будут включены в команду, будет уже слишком поздно, чтобы они могли оказать предполагаемый эффект.

Как правило, наилучшим способом решения такого рода проблем является хранение имен файлов в массиве (по одному элементу на имя файла), а не в строковой переменной; затем использование "${array[@]}"для расширения массива, при котором каждый элемент рассматривается как отдельное «слово»:

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

Обновление: если вам нужно динамически построить список файлов, это легко сделать с помощью массива:

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

решение3

Если вы используете bash, вы можете заключить имя файла в двойные кавычки, чтобы избежать использования экранированных символов:

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

используйте это вместо этого при использовании #!/bin/bashв вашем скрипте оболочки:

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

В вашем случае вы используете значения внутри переменной, но неправильно передаете переменную. Если переменная содержит пробелы, вы также должны заключить ее в двойные кавычки:

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

Если не использовать двойные кавычки, это будет похоже на использование нескольких переменных, разделенных пробелами.

Связанный контент