POSIX-совместимый способ работы со списком имен файлов, возможно, с пробелами

Я видел руководства по созданию сценариев Bash, предлагающие использовать массив для работы с именами файлов, содержащих пробелы. Однако DashAsBinSh предполагает, что массивы не переносимы, поэтому я ищу подходящий для POSIX способ работы со списками имен файлов, которые могут содержать пробелы.

Я хочу изменить приведенный ниже пример скрипта, чтобы он echo

 foo/target/a.jar foo/target/b.jar bar/target/lol whitespace.jar 

Вот сценарий

 #!/usr/bin/env sh INPUT="foo/target/a.jar foo/target/b.jar bar/target/b.jar bar/target/lol whitespace.jar" # this would be produced by a 'ls' command # We can execute the ls within the script, if it helps dostuffwith() { echo $1; }; F_LOCATIONS=$INPUT ALL_FILES=$(for f in $F_LOCATIONS; do echo `basename $f`; done) ALL_FILES=$(echo "$ALL_FILES" | sort | uniq) for f in $ALL_FILES do fpath=$(echo "$F_LOCATIONS" | grep -m1 $f) dostuffwith $fpath done 

У оболочек POSIX есть один массив: позиционные параметры ( $1 , $2 и т. Д., Все вместе называемые "$@" ).

 set -- 'foo/target/a.jar' 'foo/target/b.jar' 'bar/target/b.jar' 'bar/target/lol whitespace.jar' set -- "$@" '/another/one at the end.jar' … for jar do dostuffwith "$jar" done 

Это неудобно, потому что есть только один, и он разрушает любое другое использование позиционных параметров. Позиционные параметры являются локальными для функции, которая иногда является благословением, а иногда и проклятием.

Если ваши имена файлов не будут содержать символы новой строки, вы можете использовать символы новой строки как разделитель. Когда вы разворачиваете переменную, сначала отключите globbing с помощью set -f и установите список разделителей символов IFS чтобы содержать только новую строку.

 INPUT="foo/target/a.jar foo/target/b.jar bar/target/b.jar bar/target/lol whitespace.jar" … set -f; IFS=' ' # turn off variable value expansion except for splitting at newlines for jar in $INPUT; do set +f; unset IFS dostuffwith "$jar" # restore globbing and field splitting at all whitespace done set +f; unset IFS # do it again in case $INPUT was empty 

С элементами в вашем списке, разделенными символами новой строки, вы можете использовать многие команды обработки текста с пользой, в частности, для sort .

Не забудьте всегда ставить двойные кавычки вокруг замещений переменных, за исключением случаев, когда вы явно хотите разбить разбиение поля (а также глобусы, если вы не отключили это).

Поскольку переменная $INPUT использует новые строки как разделители, я собираюсь предположить, что ваши файлы не будут иметь новых строк в именах. Таким образом, да, есть простой способ итерации над файлами и сохранения пробелов.

Идея состоит в том, чтобы использовать встроенную оболочку read . Обычно read будет разделяться на любые пробелы, и поэтому места будут разбивать его. Но вы можете установить IFS=$'\n' и вместо этого он будет разделен только на новые строки. Таким образом, вы можете перебирать каждую строку в своем списке.

Вот самое маленькое решение, которое я мог бы придумать:

 INPUT="foo/target/a.jar foo/target/b.jar bar/target/b.jar bar/target/lol whitespace.jar" dostuffwith() { echo "$1" } echo "$INPUT" | awk -F/ '{if (!seen[$NF]++) print }' | \ while IFS=$'\n' read file; do dostuffwith "$file" done 

В основном он отправляет «$ INPUT» на awk который дедуплицирует на основе имени файла (он разбивается на / а затем печатает строку, если последний элемент ранее не был замечен). Затем, как только awk сгенерировал список путей к файлу, мы используем, while read для итерации по списку.

Я думаю, что лучший способ – использовать скобки вокруг вашей переменной следующим образом:

 dostuffwith ${fpath} 

Но я не могу гарантировать, что это posix, но он работает над AIX ksh и Linux bash. Я просто проверяю это. Поэтому, если он не совместим с posix, у него все еще есть много возможностей для работы с несколькими системами.