Імена файлів з пробілами для циклу, знайдіть команду


34

У мене є скрипт, який шукає всі файли в декількох папках і архівах, щоб визначити їх. Мій сценарій такий

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 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

4
Здається, вам слід встановити IFS=$'\n'перед циклом `for цикл, щоб він розбирав кожен рядок
kiri

Відповіді:


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тут, щоб не включати ./біт в архів.


Спасибі, це майже працює. Єдине, що це - архівування ./as як tar. ./.tar tar: ./archive.tar: file is the archive; not dumped
Ubuntuser

@Ubuntuser Ви можете додати простий чек, щоб побачитиif [[ "$FILE" == "./" ]]; then continue
kiri

@Ubuntuser: Ви можете уникнути ./біту, -printfдивіться оновлену відповідь. Однак він не повинен мати жодних змін, включений він чи ні, оскільки він просто посилається на поточний каталог. Я також включив альтернативну find/tarкомбінацію, яку ви, можливо, захочете використовувати.
Тор

Для тих, хто хоче отримати sortрезультати, перш ніж повторити їх, вам знадобиться sort -zроздільник нулів.
Адамвій

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

Доповнення подвійних цитат, елегантний, простий, красивий - спасибі!
Великий Річ


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.

це єдине рішення, яке працювало на мене. Спасибі.
codefreak


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опцію 's -exec.

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

Знайти потім виконує команду за допомогою системного виклику, щоб збереглися пробіли та нові рядки (скоріше труба, яка потребувала б цитування спеціальних символів). Зауважте, що "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
kiri

@ minerz029 Так. Це я кажу. Я не знаю, що ви маєте на увазі під [[підтримкою глобалізації. Згідно з вікі Грега , всередині не відбувається жодне глобування [[.
Джозеф Р.

Спробуйте [ "ab" == a? ] && echo "true"потім[[ "ab" == a? ]] && echo "true"
kiri

@ minerz029 Це не глобус. Це регулярні вирази (інтерпретовані слабко). Це не глобус, оскільки a*означає "слідом за будь-якою кількістю символів", а не "всі файли, імена яких починаються з aі мають будь-яку кількість символів після цього". Спробуйте [ ab = a* ] && echo true проти [[ ab == a* ]] && echo true.
Джозеф Р.

Ну добре, [[як і раніше регулярні вирази, а [ні. Повинен розгубитися
kiri
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.