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

Відповіді:


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крок робочим, як задумано.
Eero Aaltonen

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 можливий башізм в bar.sh рядок 14 (<<< тут рядок)
Ееро Аалтонен

1
@EeroAaltonen Змінив його, щоб не використовувати єресту. Зауважте, що з цією зміною whileцикл і, таким чином dostuffwith, виконується в нижній частині. Таким чином, будь-які змінні або зміни, внесені до запущеної оболонки, будуть втрачені, коли цикл завершиться. Єдина альтернатива - використовувати повний гередок, що не так вже й неприємно, але я вважав, що це буде кращим.
Патрік

Я присуджую бали більше на читанні, ніж на незначності. Це, безумовно, працює і вже +1 для цього.
Eero Aaltonen

IFS="\n"розбивається на нахил і n символів. Але в read fileрозщепленні немає. IFS="\n"все ще корисно тим, що він видаляє порожні символи з $ IFS, які в іншому випадку були б позбавлені на початку та в кінці введення. Для читання рядка канонічний синтаксис є IFS= read -r line, хоча IFS=anything read -r line(за умови, що нічого не містить пробілів) також буде працювати.
Стефан Хазелас

ой. Не впевнений, як мені це вдалося. Виправлено.
Патрік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.