Вопрос или проблема
Я пытаюсь сгенерировать файл, который могу добавить к 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[@]}"
Несколько замечаний,
- Опция
shopt nullglob
гарантирует, что если используется шаблон, который не имеет совпадений, результат будет пустая строка, а не исходное сопоставление с шаблоном, включая подстановочные символы, как литерал (попробуйтеecho bin/ansible*
, а затемecho bin/rhubarb*
; обратите внимание, что во втором случае возвращается исходная строка с подстановочным символом*
) - Я использовал
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 предлагает множество способов решения задачи парсинга данных и форматирования вывода. Используя функции и возможности оболочек, вы также получите надёжный и понятный код.
Если вы хотите углубить свои навыки, попробуйте различные подходы и выберите тот, который подходит именно вам. Таким образом, вы не только решите текущую задачу, но и расширите свои возможности в дальнейшем.