Имена файлов с пробелами для цикла, команда find

34

У меня есть скрипт, который ищет все файлы в нескольких подпапках и архивах в tar. Мой сценарий

for FILE in `find . -type f  -name '*.*'`
  do
if [[ ! -f archive.tar ]]; then

  tar -cpf archive.tar $FILE
else 
  tar -upf archive.tar $FILE 
fi
done

Команда find дает мне следующий вывод

find . -type f  -iname '*.*'
./F1/F1-2013-03-19 160413.csv
./F1/F1-2013-03-19 164411.csv
./F1-FAILED/F2/F1-2013-03-19 154412.csv
./F1-FAILED/F3/F1-2011-10-02 212910.csv
./F1-ARCHIVE/F1-2012-06-30 004408.csv
./F1-ARCHIVE/F1-2012-05-08 190408.csv

Но переменная FILE хранит только первую часть пути ./F1/F1-2013-03-19, а затем следующую часть 160413.csv .

Я пытался использовать readс циклом while,

while read `find . -type f  -iname '*.*'`;   do ls $REPLY; done

но я получаю следующую ошибку

bash: read: `./F1/F1-2013-03-19': not a valid identifier

Кто-нибудь может предложить альтернативный путь?

Обновить

Как предложено в ответах ниже, я обновил сценарии

#!/bin/bash

INPUT_DIR=/usr/local/F1
cd $INPUT_DIR
for FILE in "$(find  . -type f -iname '*.*')"
do
archive=archive.tar

        if [ -f $archive ]; then
        tar uvf $archive "$FILE"
        else
        tar -cvf $archive "$FILE"
        fi
done

Вывод, который я получаю

./test.sh
tar: ./F1/F1-2013-03-19 160413.csv\n./F1/F1-2013-03-19 164411.csv\n./F1/F1-2013-03-19 153413.csv\n./F1/F1-2013-03-19 154412.csv\n./F1/F1-2012-09-10 113409.csv\n./F1/F1-2013-03-19 152411.csv\n./.tar\n./F1-FAILED/F3/F1-2013-03-19 154412.csv\n./F1-FAILED/F3/F1-2013-03-19 170411.csv\n./F1-FAILED/F3/F1-2012-09-10 113409.csv\n./F1-FAILED/F2/F1-2011-10-03 113911.csv\n./F1-FAILED/F2/F1-2011-10-02 165908.csv\n./F1-FAILED/F2/F1-2011-10-02 212910.csv\n./F1-ARCHIVE/F1-2012-06-30 004408.csv\n./F1-ARCHIVE/F1-2011-08-17 133905.csv\n./F1-ARCHIVE/F1-2012-10-21 154410.csv\n./F1-ARCHIVE/F1-2012-05-08 190408.csv: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors
Ubuntuser
источник
4
Похоже, вы должны установить IFS=$'\n'перед циклом `for, чтобы он разбирался по каждой строке
kiri
1
Вот некоторые важные материалы: mywiki.wooledge.org/ParsingLs AND dwheeler.com/essays/filenames-in-shell.html AND unix.stackexchange.com/questions/128985/why-not-parse-ls
Сергей Колодяжный,

Ответы:

36

Использование forс find- неправильный подход, посмотрите, например, это описание о черве, которое вы открываете.

Рекомендуемый подход заключается в использовании find, whileи readкак описано здесь . Ниже приведен пример, который должен работать для вас:

find . -type f -name '*.*' -print0 | 
while IFS= read -r -d '' file; do
    printf '%s\n' "$file"
done

Таким образом, вы разделяете имена файлов с \0символами null ( ), это означает, что изменение в пространстве и другие специальные символы не вызовут проблем.

Чтобы обновить архив с файлами, которые findнаходятся, вы можете передать его вывод непосредственно tar:

find . -type f -name '*.*' -printf '%p\0' | 
tar --null -uf archive.tar -T -

Обратите внимание, что вам не нужно делать различий между тем, существует ли архив или нет tar. Также обратите внимание на использование -printfздесь, чтобы избежать включения ./бита в архив.

Тор
источник
Спасибо, это почти работает. Единственное, что это - архивация ./как tar. ./.tar tar: ./archive.tar: file is the archive; not dumped
Ubuntuser
@Ubuntuser Вы можете добавить простую проверку, чтобы увидетьif [[ "$FILE" == "./" ]]; then continue
Кири
@Ubuntuser: Вы можете избежать ./немного с -printfSEE обновленного ответом. Однако это не должно иметь никакого значения, если он включен или нет, поскольку он просто ссылается на текущий каталог. Я также включил альтернативную find/tarкомбинацию, которую вы можете использовать.
Тор
Для тех, кто хочет получить sortрезультаты перед их повторением, вам понадобится sort -zнулевой разделитель.
Adambean
13

Попробуйте процитировать forцикл так:

for FILE in "`find . -type f  -name '*.*'`"   # note the quotation marks

Без кавычек bash вообще не обрабатывает пробелы и символы новой строки ( \n) ...

Также попробуйте установить

IFS=$'\n'
харакири
источник
1
+1 за $ IFS. Это описывает символ разделителя.
Рэй
1
Это решение, которое сработало для меня. Я использовал commдля сравнения отсортированных списков файлов, и имена файлов содержали пробелы, несмотря на то, что в кавычках переменные не работали. Затем я увидел cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html и решение установки $ IFS с IFS = $ (echo -en "\ n \ b") сработало для меня.
pbhj
Добавление двойных кавычек, элегантно, просто, красиво - спасибо!
Большой Богатый
8

Это работает и проще:

find . -name '<pattern>' | while read LINE; do echo "$LINE" ; done

Благодарим Рупу ( https://github.com/rupa/z ) за этот ответ.

ShawnMilo
источник
4

В дополнение к правильному цитированию, вы можете указать findиспользовать разделитель NULL, а затем прочитать и обработать результаты в whileцикле

while read -rd $'\0' file; do
    something with "$file"
done < <(find  . -type f -name '*.*' -print0)

Это должно обрабатывать любые имена файлов, которые соответствуют POSIX - см. man find

   -print0
          True; print the full file name on the standard output, followed by a null character (instead of the newline character that  -print  uses).   This  allows  file
          names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output.  This option corresponds to the
          -0 option of xargs.
steeldriver
источник
это единственное решение, которое сработало для меня. Спасибо.
Codefreak
1
find . <find arguments> -print0 | xargs -0 grep <pattern>
user2802945
источник
1

Я сделал что-то вроде этого, чтобы найти файлы, которые могут содержать пробелы.

IFS=$'\n'
for FILE in `/usr/bin/find $DST/shared -name *.nsf | grep -v bookmark.nsf | grep -v names.nsf`; do
    file $FILE | tee -a $LOG
done

Работал как шарм :)

Скотт Б
источник
0

Я думаю, вам лучше использовать findопцию '-exec'.

find . -type f -name '*.*' -exec tar -cpf archive.tar {} +

Затем Find выполняет команду, используя системный вызов, так что пробелы и символы новой строки сохраняются (скорее, это труба, которая потребует цитирования специальных символов). Обратите внимание, что tar -c работает независимо от того, существует архив или нет, и что (по крайней мере, с помощью bash) ни {}, ни + не нужно заключать в кавычки.

Дрейк Кларрис
источник
-1

Как предложил minerz029, вам нужно процитировать расширение findкоманды. Вы также должны процитировать все замены $FILEв вашем цикле.

for FILE in "$(find . -type f  -name '*.*')"
do
    if [ ! -f archive.tar ]; then
        tar -cpf archive.tar "$FILE"
    else 
        tar -upf archive.tar "$FILE" 
    fi
done

Обратите внимание, что $()синтаксис должен быть предпочтительнее использования обратных галочек; см этого вопроса U & L . Я также удалил [[ключевое слово и заменил его [командой, потому что это POSIX.

Джозеф Р.
источник
О, [[и [, кажется, [[это новее и поддерживает больше функций, таких как глобализация и сопоставление регулярных выражений. [[только в bash, хотя, неsh
Кири
@ minerz029 Да. Это то, что я говорю. Я не знаю, что вы подразумеваете под [[болтовней. Согласно вики Грега , внутри не происходит никаких шатаний [[.
Джозеф Р.
Попробуй [ "ab" == a? ] && echo "true"тогда[[ "ab" == a? ]] && echo "true"
кири
@ minerz029 Это не глобус. Это регулярные выражения (свободно интерпретируемые). Это не глобус, потому что a*означает «a, за которым следует любое количество символов», а не «все файлы, чьи имена начинаются с aлюбого количества символов после». Попробуйте [ ab = a* ] && echo true против [[ ab == a* ]] && echo true.
Джозеф Р.
Ах, хорошо, [[все еще делает регулярные выражения, пока [нет. Должно быть, запутался
Кири