Вопрос или проблема
При работе с выводом команд, таких как locate
, которые выдают списки путей в “читаемом виде” (т.е. без \
перед пробелами), как перенаправить их вывод в другую команду?
Вывод команды $ locate [что-то]
генерирует пути с пробелами, что мешает другим программам использовать эти пути, если они содержат пробелы. Например, если я выполню
$ du -h `locate *.doc`
это вызовет ошибку для всех файлов и директорий, содержащих пробелы. (оборачивать обратные кавычки в пробелы не работает)
Какова конкретная причина, по которой вы используете locate
? Это решение, похоже, выполняет то, что вы запрашивали:
find . -type f -name '*doc' -exec du -h "{}" \;
Тем не менее, если вы действительно хотите использовать такие инструменты, как locate
или find
и передать их ввод в качестве параметров другой программе, вы можете воспользоваться выводом и вводом с разделителем NUL
, который предоставляет ряд инструментов. У locate
и find
есть опция (locate
‘s -0
и find
‘s -print0
), которая позволит вам получить более удобный для программы вывод, который xargs
предназначен для чтения с его -0
аргументом:
find . -type f -name '*doc' -print0 | xargs -0 du -h
locate -0 '*doc' | xargs -0 du -h
Если вы хотите/нужно передать ту же строку нескольким командам, нечто вроде этого сработает:
#!/usr/bin/env bash
T="${IFS}" # Сохраните внутренний разделитель полей
IFS=$'\n' # Установите его на новую строку.
while read file_line
do
echo "--"
echo "${file_line}"
echo "--"
done <<< $(locate $1)
IFS="${T}" # Верните его к оригинальному IFS.
Хорошо бы оборачивать любые переменные, которые могут содержать пробелы, в “”.
Оставляя подстановки команд ($(...)
или устаревшую форму `...`
) не заключенными в кавычки, вызывается разбивка+глоб (разбивка только в zsh) в оболочках, подобных Bourne.
Разбиение происходит на символах специального параметра $IFS
, который по умолчанию содержит пробел, табуляцию и новую строку (и NUL в zsh), что объясняет, почему ваша команда не работает с именами файлов, содержащими пробелы.
Вы можете сделать:
(IFS='
' # разбивка только по новому строке
set -o noglob # отключить глоб
du -hc -- $(locate '*.doc'))
Но это все равно не сработает, если есть имена файлов, содержащие символы новой строки. Вывод команды locate
просто нельзя обработать после.
Большинство реализаций locate
имеют опцию -0
, чтобы выводить пути файлов, разделенные NUL
. Так как NUL является единственным значением байта, которое не может встречаться в пути файла, это означает, что вывод можно обрабатывать после. Вам просто нужно разбить его по NUL.
В zsh
:
IFS=$'\0'
du -hc -- $(locate -0 '*.doc')
Или лучше, используя явный оператор для разбивки по NUL:
du -hc -- ${(0)"$(locate -0 '*.doc')"}
В bash
4.4 и выше, это может быть:
readarray -td '' files < <(locate -0 '*.doc')
du -hc -- "${files[@]}"
Тем не менее, это оставляет две проблемы:
- Если
locate
не находит ни одного файла,du -hc --
будет выполнена без аргументов, что означает, что она выдаст вам использование диска текущего рабочего каталога. - Если, с другой стороны,
locate
найдет много файлов, вы можете столкнуться с ограничением системного вызоваexecve()
и получить ошибку слишком длинный список аргументов.
Обе проблемы можно избежать, выполнив:
locate -0 '*.doc' | xargs -r0 du -hc --
(-r
и -0
являются нестандартными расширениями из GNU реализации xargs
, как -h
является нестандартным расширением из GNU реализации du
).
Однако это вводит новую проблему: xargs
будет запускать du
несколько раз для обхода ограничения execve()
, но это означает, что вы получите несколько строк total
, и также использование диска для файла, жестко связанного несколько раз, может оказаться учтенным несколько раз.
Благодаря -r
, du
не будет выполнен, если locate
не найдет ни одного файла, но это также означает, что вы не получите строку 0 total
.
С недавними версиями GNU du
, эти проблемы можно решить следующим образом:
locate -0 '*.doc' | du --files0-from=- -hc
На этот раз список файлов передается через конвейер, а не через аргументы, поэтому нет ограничения на размер аргументов. Это также означает, что не нужно выделять большие объемы памяти для хранения этого списка, и du
может начать обрабатывать его, как только locate
начнет выводить его.
Также стоит отметить, что если любые из этих файлов .doc
являются типом директория, du
будет сообщать об использовании диска самого файла плюс всех файлов внутри (являются ли они файлами .doc
или нет), рекурсивно.
Чтобы пропустить файлы типа директория, с GNU findutils
4.9 или новее, вы можете сделать:
locate -0 '*.doc' |
find -files0-from - -prune ! -type d -print0 |
du --files0-from=- -hc
(один дефис для find
, так как это так называемая (по POSIX) предикат, а не опция, и двойной дефис для du
, так как это длинная опция в стиле GNU).
Ответ или решение
Для перенаправления списка путей к файлам, содержащим пробелы, на другую команду, вы можете использовать подход, который позволяет избежать проблем, связанных с неразрывными пробелами в путях. Один из наиболее эффективных способов для этого — использование опции -0
с командами locate
или find
, что позволит выводить пути, разделенные символом NUL
. Это обеспечивает корректную обработку файловых путей, содержащих пробелы.
Пример с использованием locate
Если вы хотите найти .doc
файлы и передать их в команду du
для получения их размера, выполните следующую команду:
locate -0 '*.doc' | xargs -0 du -hc
Объяснение команд:
-
*`locate -0 ‘.doc’
** — ищет все файлы с расширением
.docи выводит их в формате, разделенном символом
NUL`, что исключает проблемы с пробелами в именах файлов. -
xargs -0 du -hc
— используетxargs
, чтобы передать полученные пути кdu
. Опция-0
позволяетxargs
корректно обрабатывать разделенныеNUL
имен файлов, что позволяет избежать проблем, связанных с пробелами.
Обработка случаев, когда не найдено файлов
Если необходимо предотвратить запуск команды du
, если в результате выполнения locate
не найдено хотя бы одного файла, вы можете добавить опцию -r
к xargs
:
locate -0 '*.doc' | xargs -r0 du -hc
Дополнительные решения
Если у вас установлена версия GNU du
4.4 или выше, есть возможность передавать список файлов напрямую через stdin
с помощью специальной опции:
locate -0 '*.doc' | du --files0-from=- -hc
Устранение дублирования вывода
Возможная проблема с вызовом du
несколько раз для большого количества файлов можно избежать, используя du --files0-from=-
, как показано выше. Это позволяет передать список файлов через стандартный ввод, что решает проблемы с максимальной длиной аргументов.
Фильтрация директорий
Если вы хотите исключить директории из обработки, вы можете использовать следующую комбинацию:
locate -0 '*.doc' |
find --files0-from=- -prune ! -type d -print0 |
du --files0-from=- -hc
Здесь мы используем find
для фильтрации директорий, чтобы избежать их учета в итоговом размере.
Заключение
Использование опции -0
для locate
и xargs
— это надежное решение для обработки файловых путей с пробелами. Следуя приведенным инструкциям, вы сможете избежать ошибок, связанных с пробелами в путях, и эффективно работать с выводом команд.