1

Я пытаюсь скопировать несколько файлов в каталог из шеллскрипта. Эти файлы содержат всевозможные "уродливые" символы, такие как пробелы, скобки и все, что не иначе. Тем не менее, я застрял, когда дело доходит до экранирования, поскольку 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 действует так странно здесь.

3 ответа3

3

Когда 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
2

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

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

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

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

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

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

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

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

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

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

Возможно, этот становится ближе:

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"

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

1

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

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"

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

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

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .