Как перенаправить список человекочитаемых путей в другую команду?

Вопрос или проблема

При работе с выводом команд, таких как 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[@]}"

Тем не менее, это оставляет две проблемы:

  1. Если locate не находит ни одного файла, du -hc -- будет выполнена без аргументов, что означает, что она выдаст вам использование диска текущего рабочего каталога.
  2. Если, с другой стороны, 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

Объяснение команд:

  1. *`locate -0 ‘.doc’** — ищет все файлы с расширением.docи выводит их в формате, разделенном символомNUL`, что исключает проблемы с пробелами в именах файлов.

  2. 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 — это надежное решение для обработки файловых путей с пробелами. Следуя приведенным инструкциям, вы сможете избежать ошибок, связанных с пробелами в путях, и эффективно работать с выводом команд.

Оцените материал
Добавить комментарий

Капча загружается...