Парсинг данных в Bash: добавление запятых после каждой строки

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

Я пытаюсь сгенерировать файл, который могу добавить к visudo. Само задание не слишком важно, так как я просто пытаюсь немного глубже разобраться в Bash. Мой текущий скрипт выглядит так:

#!/bin/bash

path=/bin/ansible*
declare -a commands=()

for command in $path; do
        commands+=($command)
done

echo ${commands[@]} > ~/commands.txt

который выводит:

/bin/ansible /bin/ansible-config /bin/ansible-connection /bin/ansible-console...

Я хотел бы, чтобы вывод скрипта выглядел так:

/bin/ansible, /bin/ansible-config, /bin/ansible-connection, /bin/ansible-console...

Я думаю, что усложняю эту задачу. Я видел, как люди используют awk и sed, но, судя по тому, что я видел, awk, похоже, работает лучше с колонками данных, а sed просто очень запутан. Любая совет был бы принят с благодарностью.

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

В тривиальном случае, когда вы выводите строковое значение с запятой после него, вы можете сделать так:

shopt -s nullglob                        # Заменить ненайденные совпадения на пустую строку
paths=(/bin/ansible*)                    # Все совпадающие элементы (или ни одного)
shopt -u nullglob                        # Если вы не установили его ранее

echo "${paths[@]/%/, }"

Однако в реалистичном случае, когда запятая является разделителем, а не терминатором, вам нужно удалить ее у последнего элемента. Этот подход избегает использования цикла:

shopt -s nullglob                        # Заменить ненайденные совпадения на пустую строку
paths=(/bin/ansible*)                    # Все совпадающие элементы (или ни одного)
shopt -u nullglob                        # Если вы не установили его ранее

delimiteds=("${paths[@]/%/, }")          # Добавить запятую и пробел к всем элементам
delimiteds[-1]="${delimiteds[-1]%, }"    # Удалить запятую и пробел у последнего элемента
echo "${delimiteds[@]}"

Несколько замечаний,

  1. Опция shopt nullglob гарантирует, что если используется шаблон, который не имеет совпадений, результат будет пустая строка, а не исходное сопоставление с шаблоном, включая подстановочные символы, как литерал (попробуйте echo bin/ansible*, а затем echo bin/rhubarb*; обратите внимание, что во втором случае возвращается исходная строка с подстановочным символом *)
  2. Я использовал echo как быстрый способ вывести элементы массива. Остерегайтесь использовать его для значений, которые начинаются с - (или в некоторых случаях содержат управляющие последовательности, такие как \r или \n)

Я думаю, что усложняю эту задачу

Вы можете так сказать. Использование подстановки, как в /bin/ansible*, уже даёт вам массив. Вам не нужно копировать элементы из этого массива в другой массив!

Так что вы можете напрямую

printf '%s, ' /bin/ansible* > ~/commands.txt
printf '\n' > ~/commands.txt

Вот и всё.

То есть, если вы хотите игнорировать тот факт, что вы потерпите неудачу, когда не будет соответствующего файла, и у вас все равно будет завершающая , .

Если вы хотите исправить обе проблемы:

shopt -s nullglob
printf '%s, ' /bin/ansible* | sed 's/, $/\n/'> ~/commands.txt

Попробуйте zsh вместо этого, который имеет встроенный оператор для соединения элементов массива с произвольными строками.

#! /bin/zsh -
ansible_commands=( /bin/ansible*(N) )
print -r -- ${(j[, ])ansible_commands} > ~/commands.txt

Или без посредственной переменной, используя анонимную функцию:

#! /bin/zsh -
function {
  print -r -- ${(j[, ])@}
} /bin/ansible*(N) > ~/commands.txt

В bash единственным встроенным оператором соединения является оператор в стиле Korn "${array[*]}", который соединяет элементы массива с первым символом $IFS, но не позволяет соединять со строками более чем одного символа, такими как ", " здесь.

В нем нет квалификатора N, и он еще не скопировал ~(N) из ksh, даже с shopt -s extglob, так что вам нужно установить опцию nullglob глобально, чтобы шаблоны с отсутствующими совпадениями расширялись в пустоту.

#! /bin/bash -
shopt -s nullglob
ansible_commands=( /bin/ansible* )
{
  printf %s "${ansible_commands[0]}"
  [ "${#ansible_commands[@]}" -le 1 ] ||
    printf ', %s' "${ansible_commands[@]:1}"
  printf '\n'
} > ~/commands.txt

Или, например, используйте perl (в любой оболочке, даже bash, хотя вы можете также использовать sh, который гарантированно найдется в любой системе):

#! /bin/sh -
perl -le 'print join ", ", </bin/ansible*>' > ~/commands.txt

Ответ или решение

Парсинг данных в Bash: добавление запятых после каждой строки

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

Проблема

Вам необходимо создать файл, который вы сможете дополнить командой visudo. Ваша текущая попытка генерирует список команд, однако результат нуждается в форматировании. Вместо обычного вывода в одну строку, вы хотите, чтобы каждый путь к командной строке был разделён запятыми.

Например, текущий результат:

/bin/ansible /bin/ansible-config /bin/ansible-connection /bin/ansible-console...

должен выглядеть так:

/bin/ansible, /bin/ansible-config, /bin/ansible-connection, /bin/ansible-console...

Решение

Есть несколько способов решения этой задачи. Мы рассмотрим их более подробно ниже.

1. Использование встроенных функций Bash

Вы можете минимизировать сложность, напрямую работая с массивом. Для этого используйте следующие команды:

#!/bin/bash

shopt -s nullglob    # Включаем использование nullglob, чтобы избежать возвращения строки с шаблоном, если совпадений нет
paths=( /bin/ansible* ) # Все соответствующие элементы (или ничего)

# Проверка на наличие совпадений
if [ ${#paths[@]} -eq 0 ]; then
    echo "Нет совпадений для /bin/ansible*"
    exit 1
fi

# Формирование строки через printf
printf '%s, ' "${paths[@]}" | sed 's/, $/\n/' > ~/commands.txt

Описание:

  • Мы используем shopt -s nullglob, чтобы подавить вывод шаблона в случае отсутствия совпадений.
  • Далее, мы формируем строку с помощью printf, добавляя запятые после каждого элемента.
  • Используем sed для удаления лишней запятой в конце.
2. Использование Perl

Если вы хотите сделать это с помощью другого подхода, рассмотрите Perl:

#!/bin/sh
perl -le 'print join ", ", glob("/bin/ansible*")' > ~/commands.txt

Описание:

  • Здесь мы используем команду glob, чтобы получить путь, а затем join для объединения этого списка с запятой.
3. Кросс-совместимые решения

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

#!/bin/bash

shopt -s nullglob
ansible_commands=( /bin/ansible* )

{
  printf "%s" "${ansible_commands[0]}"
  [ "${#ansible_commands[@]}" -le 1 ] || printf ', %s' "${ansible_commands[@]:1}"
  printf '\n'
} > ~/commands.txt

Описание:

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

Вывод

Как вы видите, базовый синтаксис Bash предлагает множество способов решения задачи парсинга данных и форматирования вывода. Используя функции и возможности оболочек, вы также получите надёжный и понятный код.

Если вы хотите углубить свои навыки, попробуйте различные подходы и выберите тот, который подходит именно вам. Таким образом, вы не только решите текущую задачу, но и расширите свои возможности в дальнейшем.

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

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