Existe algo semelhante ao echo -n no heredoc (EOF)?

12

Estou escrevendo um enorme script de fábrica de scripts, gerando muitos scripts de manutenção para meus servidores.

Até agora, escrevo algumas linhas que precisam ser escritas em uma linha com, echo -nepor exemplo,

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

isso gera um código (se houver, por exemplo, 2 servidores no meu arquivo de configuração), como

if (( ($server1_E != 0 && $server1_E != 1) || ($server2_E != 0 && $server2_E != 1) ))

Todos os outros blocos de código que não precisam dessa escrita em linha de código que eu produzo com os heredocs, já que os acho muito melhores para escrever e manter. Por exemplo, após o código superior, tenho o resto gerado como

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

Portanto, o bloco de código final parece

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

Minha pergunta
Existe uma maneira de um heredoc se comportar de maneira semelhante echo -nesem o símbolo de fim de linha?

derHugo
fonte
1
Se você precisa sudode um script, está fazendo errado: execute o script inteiro como root!
Sobremesa
1
Não porque ele também está fazendo outras coisas que eu não quero correr como root
derHugo
O uso dissosudo -u USERNAME em vez disso, consulte Como executo um comando 'sudo' dentro de um script? .
Dessert
qual é o problema de fazê-lo como eu?
DerHugo 30/11
4
Bem, é claro que há um problema: um script sudosem nome de usuário pede a senha, a menos que você a tenha digitado, há um atraso. É ainda pior se você não executar o script em um terminal, nem poderá ver a consulta da senha e ela simplesmente não funcionará. A sudo -uabordagem não apresenta nenhum desses problemas.
Dessert

Respostas:

9

Não, o heredoc sempre termina em uma nova linha. Mas você ainda pode remover a última nova linha, por exemplo, com Perl:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -plê a entrada linha por linha, executa o código especificado em -ee imprime a linha (possivelmente modificada)
  • chomp remove a nova linha final, se existir, eof retorna true no final do arquivo.
choroba
fonte
1
@Yaron se per encontrou o fim-de-arquivo (neste tubo fechado caso) que vai morder caractere de nova linha da última linha (que é o que heredoc e herestring adicionar automaticamente)
Sergiy Kolodyazhnyy
7

Existe uma maneira de um heredoc se comportar de forma semelhante a eco -ne sem o símbolo de fim de linha?

A resposta curta é que heredoc e herestrings são construídos dessa maneira, então não - aqui-doc e herestring sempre adicionam uma nova linha à direita e não há opção ou maneira nativa de desabilitá-la (talvez haja versões futuras do bash?).

No entanto, uma maneira de abordá-lo por meio de somente bash seria ler o que o heredoc fornece por meio do while IFS= read -rmétodo padrão , mas adicionar um atraso e imprimir a última linha printfsem a nova linha à direita. Aqui está o que se poderia fazer com somente 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

O que você vê aqui é o loop while usual com IFS= read -r lineabordagem, no entanto, cada linha é armazenada em uma variável e, portanto, atrasada (então pulamos a impressão da primeira linha, a armazenamos e começamos a imprimir somente quando lemos a segunda linha). Dessa forma, podemos capturar a última linha e, quando na próxima iteração readretornar o status de saída 1, é quando imprimimos a última linha sem a nova linha via printf. Verbose? sim. Mas funciona:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

Observe o $caractere de prompt sendo empurrado para a frente, pois não há nova linha. Como um bônus, esta solução é portátil e POSIX-ish, assim que deve funcionar em kshe dashbem.

$ dash ./strip_heredoc_newline.sh                                 
one
two$
Sergiy Kolodyazhnyy
fonte
6

Eu recomendo que você tente uma abordagem diferente. Em vez de reunir seu script gerado a partir de várias instruções de eco e heredocs. Eu sugiro que você tente usar um único heredoc.

Você pode usar expansão variável e substituição de comando dentro de um heredoc. Como neste exemplo:

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

Acho que isso muitas vezes leva a resultados finais muito mais legíveis do que produzir a saída peça por peça.

Também parece que você esqueceu a #!linha no início de seus scripts. A #!linha é obrigatória em qualquer script formatado corretamente. Tentar executar um script sem a #!linha funcionará apenas se você o chamar de um shell que contorna scripts mal formatados e, mesmo nesse caso, pode acabar sendo interpretado por um shell diferente do pretendido.

Kasperd
fonte
não esqueci o #!mas meu bloco de código é apenas um trecho de um script de 2000 linhas;) Não vejo agora como sua sugestão resolve meu problema ao gerar uma linha de código em um loop while ...
derHugo
@derHugo A substituição de comandos pela parte da linha em que você precisa de um loop é uma maneira de fazer isso. Outra maneira é construir essa parte em uma variável antes do heredoc. Qual dos dois é mais legível depende do tamanho do código necessário no loop, bem como de quantas linhas de heredoc você possui antes da linha em questão.
kasperd
A substituição de comando @derHugo que invoca uma função de shell definida anteriormente no script é uma terceira abordagem que pode ser mais legível se você precisar de uma quantidade significativa de código para gerar essa linha.
kasperd
@derHugo: Observe que: (1) você pode ter um loop que coloca todos os comandos necessários em uma variável; (2) Você pode usar $( ...command...)dentro de um heredoc, para inserir a saída desses comandos em linha naquele ponto no heredoc; (3) O Bash possui variáveis ​​de matriz, que você pode expandir em linha e usar substituições para adicionar itens no início / fim, se necessário. Juntos, eles tornam a sugestão de Kasperd muito mais poderosa (e seu script potencialmente muito mais legível).
Psmears
Estou usando os heredocs por causa de seu tipo de comportamento de modelo (wysiwyg). Produzir o modelo inteiro em outro local e depois passá-lo para o heredoc, na minha opinião, não facilita muito a leitura / manutenção.
DerHugo
2

Os heredocs sempre terminam com caracteres de nova linha, mas você pode simplesmente removê-lo. A maneira mais fácil que eu conheço é com o headprograma:

head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF
David Foerster
fonte
Legal eu não sabia que isso -ctambém leva valores negativos! Como se isso ainda mais do que a pearlsolução
derHugo
1

Você pode remover as novas linhas da maneira que desejar, a canalização trprovavelmente seria a mais simples. Isso removeria todas as novas linhas, é claro.

$ tr -d '\n' <<EOF 
if ((
EOF

Porém, a instrução if que você está construindo não exige isso, a expressão aritmética (( .. ))deve funcionar bem, mesmo que contenha novas linhas.

Então, você poderia fazer

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

produzindo

if ((
    ( $x_E != 0 && $x_E != 1 ) ||
    ( $y_E != 0 && $y_E != 1 ) ||
    0 )) ; then 
    echo do something
fi

Construí-lo em um loop ainda torna feio, no entanto. O || 0que há, naturalmente, de modo que nós não precisa caso especial a primeira ou a última linha.


Além disso, você tem isso | sudo tee ...em todas as saídas. Acho que poderíamos nos livrar disso abrindo um redirecionamento apenas uma vez (com substituição de processo) e usando isso para a impressão posterior:

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

E, francamente, ainda acho que a criação de nomes de variáveis ​​a partir de variáveis ​​no script externo (assim \$${name}_E) é um pouco feia e provavelmente deve ser substituída por uma matriz associativa .

ilkkachu
fonte