Есть ли что-то похожее на echo -n в heredoc (EOF)?

12

Я пишу огромный скрипт-фабрику сценариев, генерирующую множество сценариев обслуживания для моих серверов.

До сих пор я пишу несколько строк, которые нужно записать в одну строку, 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без символа конца строки?

derHugo
источник
1
Если вам нужен sudoскрипт, вы делаете это неправильно: вместо этого запустите весь скрипт как root!
десерт
1
Нет, потому что он также делает другие вещи, которые я не хочу запускать как root
derHugo
Использование sudo -u USERNAMEна том , что вместо того, чтобы , увидеть Как запустить команду «» SUDO внутри сценария? ,
десерт
в чем проблема делать это, как я?
derHugo
4
Ну, конечно, есть проблема: сценарий sudoбез имени пользователя запрашивает пароль, если вы не ввели его с задержкой. Еще хуже, если вы не запустите скрипт в терминале, тогда вы даже не увидите запрос пароля, и он просто не будет работать. sudo -uПодход не имеет какой - либо из этих проблем.
десерт

Ответы:

9

Нет, heredoc всегда заканчивается новой строкой. Но вы все равно можете удалить последний символ новой строки, например, с помощью Perl:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -pчитает строку ввода построчно, выполняет код, указанный в -e, и печатает (возможно, измененную) строку
  • chomp удаляет последний символ новой строки, если он существует, eof возвращает true в конце файла.
choroba
источник
1
@ Yaron, если для каждого обнаруженного конца файла (в данном случае закрытая труба) он будет скомпилировать символ новой строки с последней строки (что автоматически добавляется heredoc и herestring)
Сергей Колодяжный
7

Есть ли способ, при котором heredoc ведет себя подобно echo -ne без символа конца строки?

Короткий ответ заключается в том, что heredoc и herestrings просто создаются таким образом, поэтому 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$
Сергей Колодяжный
источник
6

Я рекомендую вам попробовать другой подход. Вместо того, чтобы соединять ваш сгенерированный скрипт из нескольких эхо-операторов и heredocs. Я предлагаю вам попробовать использовать один heredoc.

Вы можете использовать расширение переменных и подстановку команд внутри heredoc. Как в этом примере:

#!/bin/bash
cat <<EOF
$USER $(uname)
EOF

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

Также похоже, что вы забыли #!строку в начале ваших сценариев. #!Линия является обязательным в любом правильно отформатированных сценариев. Попытка запустить скрипт без #!строки будет работать только в том случае, если вы вызываете его из оболочки, которая работает с плохо отформатированными скриптами, и даже в этом случае может произойти интерпретация интерпретатором другой оболочки, чем вы предполагали.

kasperd
источник
еще не забыл, #!но мой блок кода - это всего лишь один фрагмент из сценария с 2000 строками;) Сейчас я не вижу, как ваше предложение решает мою проблему генерации одной строки кода в цикле while ...
derHugo
@derHugo Командная замена части строки, где вам нужен цикл, является одним из способов сделать это. Другой способ - создать эту часть в переменной до heredoc. Какой из этих двух вариантов наиболее удобен для чтения, зависит от длины кода, необходимого в цикле, а также от количества строк heredoc до рассматриваемой строки.
Касперд
@derHugo Подстановка команд, вызывающая функцию оболочки, определенную ранее в сценарии, является третьим подходом, который может быть более читабельным, если вам требуется значительное количество кода для генерации этой одной строки.
Касперд
@derHugo: Обратите внимание: (1) у вас может быть цикл, который помещает все необходимые команды в переменную; (2) Вы можете использовать $( ...command...)внутри heredoc, чтобы вставить вывод этих команд, встроенный в этот момент в heredoc; (3) В Bash есть переменные-массивы, которые вы можете расширить встроенными и использовать подстановки, чтобы добавить вещи в начало / конец, если это необходимо. Вместе они делают предложение Касперда намного более мощным (и ваш сценарий потенциально намного более читабельным).
psmears
Я использую heredocs из-за своего поведения в шаблонах (wysiwyg). На мой взгляд, создание всего шаблона в другом месте и передача его в heredoc не очень-то упрощает чтение и поддержку.
derHugo
2

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

head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF
Дэвид Фёрстер
источник
Круто я не знал, что -cтоже принимает отрицательные значения! Так даже больше, чем pearlрешение
derHugo
1

Вы можете удалить переводы строк так, как вам нравится, и 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) немного уродливо, и его, вероятно, следует заменить ассоциативным массивом .

ilkkachu
источник