Чи є щось схоже на 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

Моє запитання
Чи існує спосіб, як гередок поводиться подібним чином echo -neбез символу кінця рядка?


1
Якщо вам потрібен sudoсценарій, ви робите це неправильно: замість цього запустіть увесь сценарій як root!
десерт

1
Ні, тому що це також робить інші речі, які я не хочу запускати як root
derHugo

Використання sudo -u USERNAMEна те , що замість того, щоб , побачити Як запустити команду «» SUDO всередині сценарію? .
десерт

в чому проблема, як це зробити я?
derHugo

4
Ну, звичайно, є проблема: сценарій sudoбез імені користувача запитує пароль, якщо ви не введете його там, де затримка. Ще гірше, якщо ви не запустите скрипт у терміналі, тоді ви навіть не можете побачити запит пароля, і він просто не спрацює. У sudo -uпідходу немає жодної з цих проблем.
десерт

Відповіді:


9

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

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -pзчитує рядок введення за рядком, запускає код, зазначений у -e, та друкує (можливо, модифікований) рядок
  • chomp видаляє остаточний новий рядок, якщо він існує, eof повертає true у кінці файлу.

1
@Yaron, якщо на кожен кінець файлу (у цьому випадку закрита труба) він буде чіп новим рядком із останнього рядка (це те, що гередок та герестринг додаються автоматично)
Сергій Колодяжний

7

Чи існує спосіб, коли гередок поводиться схоже на ехо -не без символу кінця рядка?

Коротка відповідь полягає в тому, що heredoc і herestrings просто побудовані таким чином, так що ні - тут-doc і herestring додаватимуть останній рядок завжди, і немає жодного варіанту чи рідного способу відключити його (можливо, це буде в майбутніх баш-релізах?).

Однак один із способів наблизитись до нього лише за допомогою bash - це прочитати те, що дає heredoc за допомогою стандартного while IFS= read -rметоду, але додати затримку та надрукувати останній рядок за допомогою printfбез зворотнього нового рядка. Ось, що можна зробити лише з bash:

#!/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

Те, що ви бачите тут, - це звичайний цикл з IFS= read -r lineпідходом, однак кожен рядок зберігається у змінній і, таким чином, затримується (тому ми пропускаємо друк першого рядка, зберігаємо його та починаємо друкувати лише тоді, коли ми прочитали другий рядок). Таким чином ми можемо зафіксувати останній рядок, і коли на наступній ітерації readповертається статус виходу 1, тоді ми друкуємо останній рядок без нового рядка через printf. Багатослівний? так. Але це працює:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

Зауважте, що $символ підкатки висувається вперед, оскільки немає нового рядка. Як бонус, це рішення є портативним та POSIX-ish, тому воно має працювати kshі dashв.

$ dash ./strip_heredoc_newline.sh                                 
one
two$

6

Рекомендую спробувати інший підхід. Замість того, щоб зібрати генерований сценарій разом із кількох висловлювань ехо та гередок. Я пропоную вам спробувати використовувати один гередок.

Ви можете використовувати змінне розширення та заміну команд всередині heredoc. Як у цьому прикладі:

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

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

Крім того, схоже, що ви забули #!рядок на початку своїх сценаріїв. #!Лінія є обов'язковим в будь-якому правильно відформатовані сценаріїв. Спроба запустити скрипт без #!рядка спрацює лише в тому випадку, якщо ви викликаєте його з оболонки, яка працює навколо погано відформатованих сценаріїв, і навіть у такому випадку це може спричинити інтерпретацію іншої оболонки, ніж ви задумали.


я не забув, #!але мій блок коду - це лише один фрагмент із сценарію 2000 рядків;) Я не бачу зараз, як ваша пропозиція вирішує мою проблему з генеруванням одного рядка коду в циклі часу ...
derHugo

Заміна команди @derHugo для частини рядка, де вам потрібна петля, - це один із способів зробити це. Інший спосіб - побудувати цю частину в змінній перед гередоком. Який із двох є найчитабельнішим, залежить від довжини коду, необхідного в циклі, а також від кількості рядків heredoc перед відповідним рядком.
kasperd

Заміна @derHugo Command, що викликає функцію оболонки, визначену раніше в сценарії, є третім підходом, який може бути більш читабельним, якщо вам потрібна значна кількість коду для створення цього одного рядка.
kasperd

@derHugo: Зауважте, що: (1) Ви можете мати цикл, який ставить усі необхідні команди в змінну; (2) Ви можете використовувати $( ...command...)всередині heredoc, щоб вставити висновок цих команд, введених у цей момент, у heredoc; (3) У Bash є змінні масиву, за допомогою яких ви можете розширити вбудований рядок, а також використовувати підстановки для додавання речей на початку / кінці, якщо необхідно. Разом вони роблять пропозицію kasperd набагато потужнішою (а ваш сценарій потенційно набагато читабельнішим).
psmears

Я використовую гередоки через їхню поведінку шаблону (wysiwyg). Створення всього шаблону в іншому місці та, ніж передавання його гередоку, на мою думку, насправді не полегшує читання / підтримку.
derHugo

2

Гередоки завжди закінчуються символами нового рядка, але їх можна просто видалити. Найпростіший спосіб, який я знаю, це з 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) трохи некрасиво, і його, мабуть, слід замінити асоціативним масивом .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.