Я пишу на огромном скрипте-сценарии, генерирующем множество сценариев обслуживания для моих серверов.
До сих пор я пишу некоторые строки, которые нужно записать в одной строке с помощью 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
-p читает ввод строки за строкой, запускает код, указанный в -e, и печатает (возможно, line chomp удаляет окончательную новую строку, если она существует, eof возвращает true в конце файла. 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
Я нахожу, что это часто приводит к гораздо более читаемым конечным результатам, чем к выпуску по частям.
Также похоже, что вы забыли #! в начале ваших скриптов. Строка #! обязательна в любых правильно отформатированных сценариях. Попытка запустить скрипт без строки #! будет работать, только если вы вызовете его из оболочки, которая работает с плохо отформатированными сценариями, и даже в этом случае она может получить интерпретацию другой оболочки, чем вы предполагали.
Короткий ответ заключается в том, что heredoc и herestrings просто построены таким образом, поэтому no - here-doc и herestring будут добавлять конечную новую строку всегда, и нет возможности или собственный способ ее отключить (возможно, будет в будущих релизах bash?).
Однако один из способов приблизиться к нему с помощью только bash - это прочитать, что 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-ish, поэтому оно должно работать и в ksh и dash.
$ dash ./strip_heredoc_newline.sh
one
two$
Нет, heredoc всегда заканчивается новой строкой. Но вы все равно можете удалить последнюю строку новой строки, например, с помощью Perl:
cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
-p читает ввод строки за строкой, запускает код, указанный в -e, и печатает (возможно, line chomp удаляет окончательную новую строку, если она существует, eof возвращает true в конце файла. 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
Я нахожу, что это часто приводит к гораздо более читаемым конечным результатам, чем к выпуску по частям.
Также похоже, что вы забыли #! в начале ваших скриптов. Строка #! обязательна в любых правильно отформатированных сценариях. Попытка запустить скрипт без строки #! будет работать, только если вы вызовете его из оболочки, которая работает с плохо отформатированными сценариями, и даже в этом случае она может получить интерпретацию другой оболочки, чем вы предполагали.
Короткий ответ заключается в том, что heredoc и herestrings просто построены таким образом, поэтому no - here-doc и herestring будут добавлять конечную новую строку всегда, и нет возможности или собственный способ ее отключить (возможно, будет в будущих релизах bash?).
Однако один из способов приблизиться к нему с помощью только bash - это прочитать, что 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-ish, поэтому оно должно работать и в ksh и dash.
$ dash ./strip_heredoc_newline.sh
one
two$