Двойные кавычки против расширения имени файла звездочкой в ​​Bash

В каталогах ~/temp/a/foo/ и ~/temp/b/foo foo/ у меня есть файлы с именами bar1, bar2, bar bar1, bar bar2 и т. д.

Я пытаюсь написать строку Bash, которая копирует все эти файлы в каталог, содержащий «foo» в качестве последней части имени, в папку над соответствующей папкой «foo».

Пока в именах файлов нет пробелов, это простая задача, но две следующие команды не работают при работе с каталогом foo foo:

for dir in `find . -type d -name '*foo'` ; do cp $dir/* "$(echo $dir|sed 's_foo__g')" ; done

(Команда cp не видит последние foo из "foo foo" как часть того же имени каталога.)

for dir in `find . -type d -name '*foo'` ; do cp "$dir/*" "$(echo $dir|sed 's_foo__g')" ; done

("$dir/*" не расширяется.)

Попытки заменить $dir/* на "$(echo $dir/*)" оказались еще менее успешными.

Есть ли простой способ расширить $dir/*, чтобы cp понял?


person uvett    schedule 16.03.2013    source источник
comment
См. mywiki.wooledge.org/DontReadLinesWithFor.   -  person Charles Duffy    schedule 17.03.2013


Ответы (3)


Цикл for не только неверен, но и sed не подходит для этой работы.

while IFS= read -r -d '' dir; do
  cp "$dir" "${dir/foo/}"
done < <(find . -type d -name '*foo' -print0)

Использование -print0 на findIFS= read -r -d '') гарантирует, что имена файлов с символами новой строки не испортят вам настроение.

Использование конструкции < <(...) гарантирует, что если внутри вашего цикла устанавливаются переменные, изменяется состояние каталога или выполняются аналогичные действия, эти изменения сохранятся (правая часть конвейера находится в подоболочке в bash, поэтому конвейер в цикле while будет означать, что любые изменения в состоянии оболочки, сделанные внутри этого цикла while, в противном случае будут отброшены при его выходе).

Использование ${dir/foo/} намного эффективнее, чем вызов sed, так как замена строки выполняется внутри bash.

person Charles Duffy    schedule 16.03.2013
comment
Вы уверены, что IFS= там нужен? Отличный пост кстати! - person janos; 17.03.2013
comment
Несмотря на предложение janos принять этот ответ в качестве решения, оказывается, что код на самом деле не перемещает файлы. Bash 4.2.24 жалуется, что cp не может stat ''. Я не уверен на 100%, что делает строка cp "$dir" "${dir/foo/}". Действительно ли он пытается копировать файлы в существующий каталог выше каталога foo? - person uvett; 17.03.2013
comment
@uvett Дэн указал на ошибку. Если это не поможет, убедитесь, что ваш файл начинается с #!/bin/bash; этот код не будет работать с #!/bin/sh. - person Charles Duffy; 17.03.2013
comment
@janos IFS= предотвращает удаление конечных пробелов. (Этого также можно было бы избежать, не указывая dir и разрешая назначать прочитанное содержимое $REPLY). - person Charles Duffy; 17.03.2013
comment
Извините, не повезло. Пока я использую cp -r, а каталог foo содержит символы, предшествующие foo, я могу перемещать файлы с помощью этих строк, но в этом случае строка cp -r "$dir" "${dir/foo/}" (очевидно) создает новый каталог на основе старого имени каталога, вычитая foo часть. С каталогами, названными только foo, команда завершается ошибкой. Мои первые мысли о том, как решить эту проблему, точно такие же, как те, которые заставили меня создать эту тему: замена cp "$dir" "${dir/foo/}" Чарльза на cp "$dir/*" "${dir/foo}" (бесполезно, потому что кавычки предотвращают расширение звездочки). - person uvett; 17.03.2013
comment
@uvett О. В этом случае нет необходимости в замене строк. Внутри цикла while, управляемого find: cp -r "$dir"/* "$dir/.." - person Charles Duffy; 17.03.2013
comment
@ Чарльз Даффи Очень мило и надежно, еще раз спасибо. cp -r оказался ненужным для проблемы, которую я описал в своем вопросе. Не уверен, что код в этом ответе следует редактировать, поскольку часть замены строки можно (должно) исключить - в то же время она является предметом некоторых интересных комментариев. - person uvett; 17.03.2013

Проблема здесь не с cp, а с for, потому что по умолчанию он разбивает вывод вашей подоболочки по словам, а не по именам каталогов.

Ленивый обходной путь состоит в том, чтобы вместо этого использовать while и обрабатывать список каталогов построчно следующим образом:

find . -type d -name '*foo' | while read dir; do cp "$dir"/* "$(echo $dir | sed 's_foo__g')" ; done

Это должно решить вашу проблему с пробелами, но это ни в коем случае не надежное решение.

Смотрите ответ Чарльза Даффи для более точного решения.

person janos    schedule 16.03.2013
comment
Почти верно, но вы действительно должны использовать разделитель NUL для содержимого конвейера, иначе злонамеренно созданное имя файла, содержащее новую строку, может привести к копированию любого произвольного файла. - person Charles Duffy; 17.03.2013
comment
...кстати, также неверно, что dir никогда не будет содержать пробелы как артефакт того, как работает for; разделение слов не является частью семантики for, и если IFS не содержит пробелов, вывод разделения строк может содержать пробелы. Тем не менее полагаться на разбиение строк неправильно по другим причинам (например, потому что расширение глобуса происходит одновременно). - person Charles Duffy; 17.03.2013
comment
Спасибо, похоже, это работает для всех моих реальных проблем с копированием файлов. То, что на моем упрощенном примере это на самом деле не работает, — забавная деталь. (sed удаляет обе foo в имени папки foo foo). - person uvett; 17.03.2013
comment
@CharlesDuffy почти прав ... немного далек от истины, а :-) Но именно поэтому я добавил ненадежный бит. Спасибо за советы, перефразирую то, что писал про for. - person janos; 17.03.2013
comment
@uvett Я призываю вас принять ответ Чарльза Даффи вместо моего. Это более точно, лучше объяснено и заботится обо всех крайних случаях. - person janos; 17.03.2013

Переименуйте ~/temp/b/foo foo/ во что-нибудь без пробелов, например. ~/temp/b/foo_foo/ и снова сделайте то, что пытались сделать. После этого вы можете переименовать его обратно в исходное, с пробелом, если вам действительно нужно. КСТАТИ. сам я никогда не использую имена файлов, содержащие пробелы, просто чтобы избежать осложнений, подобных той, с которой вы столкнулись сейчас.

person piokuc    schedule 16.03.2013
comment
Я попытался упростить задачу, связанную с десятками папок и сотнями файлов с уродливыми именами. Переименование их кажется неоптимальным вариантом, тем более, что позже мне придется восстанавливать имена папок. - person uvett; 17.03.2013