Безопасно обойдите файлы, когда в именах бывают пробелы или переводы строк
Задача очистки сжимает каждый файл под ./reports, но цикл ниже калечит пути с пробелом — Q3 report.csv дробится на Q3 и report.csv, и каждая половина падает. В именах бывают также табы и переводы строк.
Ограничения: не разбирайте вывод ls; цикл должен выдержать любой байт имени, кроме NUL. Обойдите так, чтобы каждый путь попадал в gzip целым.
for f in $(find ./reports -type f); do
gzip "$f"
done
Перепишите цикл, чтобы он обходил файлы безопасно.
for f in $(find …) небезопасен: подстановка проходит word-splitting по IFS, поэтому путь с пробелом или переводом строки распадается на итерации, а glob в имени раскрывается. Обходите NUL-разделённый вывод: find … -print0 | while IFS= read -r -d '' f; do …; done. Символ \0 не встречается в имени, а IFS=/-r сохраняют имя дословно.
- ✗Обходить через
for f in $(find …)или$(ls), что дробит по каждому пробелу - ✗Закавычивать всю подстановку — тогда выходит одно огромное единое значение
- ✗Опускать
IFS=или-r, из-за чегоreadрежет пробелы или ест обратные слэши
- →Почему
-print0идёт в паре сread -d '', а не с разделителем-переводом строки? - →Чем
find -exec gzip {} +отличался бы от циклаwhile readздесь?
Решение
Причина бага — $(find …) проходит word-splitting по IFS (пробел, таб, перевод строки) до того, как значение попадёт в f. Поэтому Q3 report.csv превращается в две итерации. Кавычки на "$f" внутри тела не спасают: деление уже случилось на этапе подстановки. Плюс имя-glob вроде *.csv раскрылось бы.
Надёжный обход — по разделителю NUL (\0), единственному байту, который не может встретиться в имени файла:
find ./reports -type f -print0 | while IFS= read -r -d '' f; do
gzip "$f"
done
трактовать \ как экранирование.
-print0—findразделяет пути символом\0, а не переводом строки.read -d ''— читает до\0;IFS=отключает обрезку пробелов;-rне даёт"$f"— цитируем при передаче вgzip.
Альтернатива без цикла — find ./reports -type f -exec gzip {} +, тоже безопасная к пробелам. Никогда не парсите ls для итерации.