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

14

Я видел руководства по написанию сценариев 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
Ээро Аалтонен
источник
То же самое можно сделать и на SO: stackoverflow.com/questions/6499486/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Ответы:

8

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

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

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

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

Жиль "ТАК - перестань быть злым"
источник
Хороший ответ и объяснение. Я собираюсь отметить это как принятый, потому что это заставляет оригинальный sort | uniqшаг работать как предназначено.
Ээро Аалтонен
5

Поскольку в вашей $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для перебора списка.

Патрик
источник
$ checkbashisms bar.sh возможный bashism в строке bar.sh 14 (<<< здесь строка)
Eero Aaltonen
1
@EeroAaltonen Изменено, чтобы не использовать herestring. Обратите внимание, что с этим изменением whileцикл и, следовательно dostuffwith, выполняется в подоболочке. Поэтому любые переменные или изменения, внесенные в работающую оболочку, будут потеряны после завершения цикла. Единственная альтернатива - использовать полный heredoc, что не так уж и неприятно, но я подумал, что это будет предпочтительнее.
Патрик
Я присуждаю баллы, основанные больше на удобочитаемости, чем на малости. Это конечно работает и уже +1 за это.
Ээро Аалтонен
IFS="\n"разбивает на обратную косую черту и n символов. Но в read fileэтом нет разделения. IFS="\n"все еще полезно в этом, это удаляет пустые символы из $ IFS, которые иначе были бы удалены в начале и конце ввода. Для того, чтобы прочитать строку, канонический синтаксис IFS= read -r line, хотя IFS=anything read -r line(если ничего не содержит пробелов) будет работать.
Стефан Шазелас
упс. Не уверен, как мне это удалось. Исправлена.
Патрик