Я пишу огромный скрипт-фабрику сценариев, генерирующую множество сценариев обслуживания для моих серверов.
До сих пор я пишу несколько строк, которые нужно записать в одну строку с echo -ne
, например,
echo -n "if (( " | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
# Generate exitCode check for each Server
IFS=" "
COUNT=0
while read -r name ipAddr
do
if(($COUNT != 0))
then
echo -n " || " | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
fi
echo -n "(\$"$name"_E != 0 && \$"$name"_E != 1)" | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
COUNT=$((COUNT+1))
JOBSDONE=$((JOBSDONE+1))
updateProgress $JOBCOUNT $JOBSDONE
done <<< "$(sudo cat /root/.virtualMachines)"
echo " ))" | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
это генерирует мне (если, например, в моем файле конфигурации есть 2 сервера), например,
if (( ($server1_E != 0 && $server1_E != 1) || ($server2_E != 0 && $server2_E != 1) ))
Все остальные блоки кода, которые не нуждаются во встроенном написании кода, который я создаю с помощью heredocs так как я считаю, что их лучше писать и поддерживать. Например. после верхнего кода у меня есть остальные, сгенерированные как
cat << EOF | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
then
# Print out ExitCode legend
echo " ExitCode 42 - upgrade failed"
echo " ExitCode 43 - Upgrade failed"
echo " ExitCode 44 - Dist-Upgrade failed"
echo " ExitCode 45 - Autoremove failed"
echo ""
echo ""
fi
EOF
Таким образом, последний блок кода выглядит как
if (( ($server1_E != 0 && $server1_E != 1) || ($server2_E != 0 && $server2_E != 1) ))
then
# Print out ExitCode legend
echo " ExitCode 42 - upgrade failed"
echo " ExitCode 43 - Upgrade failed"
echo " ExitCode 44 - Dist-Upgrade failed"
echo " ExitCode 45 - Autoremove failed"
echo ""
echo ""
fi
Мой вопрос
Есть ли способ, при котором heredoc ведет себя подобно echo -ne
без символа конца строки?
Нет, heredoc всегда заканчивается новой строкой. Но вы все равно можете удалить последнюю новую строку, например, с помощью Perl:
cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
Есть ли способ, с помощью которого heredoc ведет себя подобно echo -ne без символа конца строки?
Краткий ответ заключается в том, что heredoc и управляющие строки просто создаются таким образом , поэтому no - here-doc и herestring всегда будут добавлять завершающий символ новой строки, и нет никакого варианта или встроенного способа его отключения (возможно, это будет в будущих выпусках bash?).
Однако, один из способов приблизиться к нему через bash-only, это прочитать то, что дает heredoc с помощью стандартного метода while IFS= read -r
, но добавить задержку и вывести последнюю строку через printf
без завершающего символа новой строки. Вот что можно сделать с bash-only:
#!/usr/bin/env bash
while true
do
IFS= read -r line || { printf "%s" "$delayed";break;}
if [ -n "$delayed" ];
then
printf "%s\n" "$delayed"
fi
delayed=$line
done <<EOF
one
two
EOF
Здесь вы видите обычный цикл while с подходом IFS= read -r line
, однако каждая строка сохраняется в переменной и, таким образом, задерживается (поэтому мы пропускаем печать сохраните его и начните печатать только после прочтения второй строки). Таким образом, мы можем захватить последнюю строку, и когда на следующей итерации read
возвращает состояние выхода 1, именно тогда мы печатаем последнюю строку без перевода строки через printf
. Подробный ? да. Но это работает:
$ ./strip_heredoc_newline.sh
one
two$
Обратите внимание, что символ подсказки $
выдвигается вперед, так как нет новой строки. В качестве бонуса, это решение является переносимым и POSIX-ишем, поэтому оно должно работать также в ksh
и dash
.
$ dash ./strip_heredoc_newline.sh
one
two$
Heredocs всегда заканчиваются символами новой строки, но вы можете просто удалить это. Самый простой способ, который я знаю, это программа head
:
head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF
Вы можете удалить переводы строк любым удобным для вас способом, возможно, проще всего подать трубку к tr
. Это, конечно, удалит все новые строки.
$ tr -d '\n' <<EOF
if ((
EOF
Но для создаваемого вами оператора if это не требуется, арифметическое выражение (( .. ))
должно работать нормально, даже если оно содержит символы новой строки.
Таким образом, вы могли бы сделать
cat <<EOF
if ((
EOF
for name in x y; do
cat << EOF
( \${name}_E != 0 && \${name}_E != 1 ) ||
EOF
done
cat <<EOF
0 )) ; then
echo do something
fi
EOF
, создавая
if ((
( $x_E != 0 && $x_E != 1 ) ||
( $y_E != 0 && $y_E != 1 ) ||
0 )) ; then
echo do something
fi
Построение его в цикле все еще выглядит ужасно. || 0
, конечно же, есть, поэтому нам не нужно специально выделять первую или последнюю строку.
Кроме того, у вас есть | sudo tee ...
в каждом выходе. Я думаю, что мы могли бы избавиться от этого, открыв перенаправление только один раз (с заменой процесса) и использовав его для последующей печати:
exec 3> >(sudo tee outputfile &>/dev/null)
echo output to the pipe like this >&3
echo etc. >&3
exec 3>&- # close it in the end
И, честно говоря, я все еще думаю, что создание имен переменных переменные во внешнем скрипте (например, \${name}_E
) немного уродливы, и, вероятно, их следует заменить ассоциативным массивом .
Я рекомендую попробовать другой подход. Вместо того, чтобы соединять ваш сгенерированный скрипт из нескольких эхо-операторов и heredocs. Я предлагаю вам попробовать использовать один heredoc.
Вы можете использовать расширение переменных и подстановку команд внутри heredoc. Как в этом примере:
#!/bin/bash
cat <<EOF
$USER $(uname)
EOF
Я обнаружил, что это часто приводит к гораздо более читаемым конечным результатам, чем создание выходных данных по частям.
Также похоже, что вы забыли строку #!
в начале ваших сценариев. Строка #!
обязательна в любых правильно отформатированных скриптах. Попытка запустить сценарий без строки #!
будет работать только в том случае, если вы вызываете его из оболочки, которая работает с плохо отформатированными сценариями, и даже в этом случае она может быть интерпретирована другой оболочкой, чем вы предполагали.