Como eu trago o texto HEREDOC para uma variável de script shell?

6

Estou tentando trazer o texto HEREDOC para uma variável de script de shell de uma maneira compatível com POSIX. Eu tentei assim:

#!/bin/sh

NEWLINE="
"

read_heredoc2() {
  while IFS="$NEWLINE" read -r read_heredoc_line; do
    echo "${read_heredoc_line}"
  done
}

read_heredoc2_result="$(read_heredoc2 <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ' _ \| | | | '_ \| |/ _' |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|



HEREDOC
)"

echo "${read_heredoc2_result}"

Isso produziu o seguinte, que está errado:

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ' _ \| | | | '_ \| |/ _' |/ __/ _ \/ _ \| '_ \| | | '_ \ / _  | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|

O seguinte funciona, mas eu não gosto do quão desajeitado é usando uma variável de saída aleatória:

#!/bin/sh

NEWLINE="
"

read_heredoc1() {
  read_heredoc_first=1
  read_heredoc_result=""
  while IFS="$NEWLINE" read -r read_heredoc_line; do
    if [ ${read_heredoc_first} -eq 1 ]; then
      read_heredoc_result="${read_heredoc_line}"
      read_heredoc_first=0
    else
      read_heredoc_result="${read_heredoc_result}${NEWLINE}${read_heredoc_line}"
    fi
  done
}

read_heredoc1 <<'HEREDOC'

                        _                            _ _            
                       | |                          | (_)           
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___ 
 | '_ ' _ \| | | | '_ \| |/ _' |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |                                                
            |___/|_|                                                



HEREDOC

echo "${read_heredoc_result}"

Saída correta:

                        _                            _ _            
                       | |                          | (_)           
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___ 
 | '_ ' _ \| | | | '_ \| |/ _' |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |                                                
            |___/|_|                                                

Alguma idéia?

    
por Kevin 28.01.2017 / 06:37

4 respostas

9

O problema é que, no Bash , dentro de $( ... ) as seqüências de escape (e outras) são analisadas, mesmo que o próprio heredoc não as tenha. Você recebe uma linha duplicada porque \ escapa da quebra de linha. O que você está vendo é realmente um problema de análise no Bash - outros shells não fazem isso. Backticks também podem ser um problema em versões mais antigas. Eu tenho confirmado que este é um bug no Bash , e ele será ser corrigido em versões futuras.

Você pode pelo menos simplificar sua função drasticamente:

func() {
    res=$(cat)
}
func <<'HEREDOC'
...
HEREDOC

Se você quiser escolher a variável de saída, ela pode ser parametrizada:

func() {
    eval "$1"'=$(cat)'
}
func res<<'HEREDOC'
...
HEREDOC

Ou um bastante feio sem eval :

{ res=$(cat) ; } <<'HEREDOC'
...
HEREDOC

Os {} são necessários, em vez de () , para que a variável permaneça disponível posteriormente.

Dependendo da frequência com que você fará isso e com qual finalidade, você pode preferir uma ou outra dessas opções. O último é o mais conciso para um único.

Se você for capaz de usar zsh , sua substituição de comando original + heredoc funcionará como está, mas você também pode reduzir ainda mais:

x=$(<<'EOT'
...
EOT
)

O Bash não suporta isso e eu não acho que qualquer outro shell que experimentaria o problema que você está tendo também faz.

    
por 28.01.2017 / 08:35
4

Sobre a solução OP:

  • Você não precisa de um eval para atribuir uma variável se permitir que alguma variável constante seja usada.

  • a estrutura geral de chamar uma função que recebe o HEREDOC também pode ser implementada.

Uma solução que funciona em todos os shells (razoáveis) com os dois itens resolvidos é esta:

#!/bin/bash
nl="
"

read_heredoc(){
    var=""
    while IFS="$nl" read -r line; do
        var="$var$line$nl"
    done 
}


read_heredoc <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ' _ \| | | | '_ \| |/ _' |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|

HEREDOC

read_heredoc2_result="$str"

printf '%s' "${read_heredoc2_result}"

Uma solução para a pergunta original.

Uma solução que funciona desde o bash 2.04 (e recente zsh, lksh, mksh).
Veja abaixo uma versão mais portátil (POSIX).

#!/bin/bash
read_heredoc() {
    IFS='' read -d '' -r var <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ' _ \| | | | '_ \| |/ _' |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|



HEREDOC

}

read_heredoc
echo "$var"

O comando principal

IFS='' read -d '' -r var <<'HEREDOC'

funciona da seguinte forma:

  1. A palavra HEREDOC é (única) citada para evitar qualquer expansão do texto que se segue.
  2. O conteúdo "here doc" é exibido no stdin com << .
  3. A opção -d '' força read a fazer slurp de todo o conteúdo do "here doc".
  4. A opção -r evita a interpretação de caracteres citados com barra invertida.
  5. O comando principal é semelhante a read var .
  6. E o último detalhe é IFS='' , o que evitará que a leitura remova os caracteres iniciais ou finais na guia padrão do IFS: espaço nova linha

Em ksh, o valor nulo para a opção -d '' não funciona.
Como solução alternativa, se o texto não tiver "retorno de carro", um -d $'\r' funciona (se um $'\r' for adicionado ao final de cada linha, é claro).

Um requisito adicionado (em comentários) é gerar uma solução compatível com POSIX.

POSIX

Estendendo a ideia para executá-lo apenas com opções POSIX.
Isso significa principalmente que não há -d para read . Isso força uma leitura para cada linha. Isso, por sua vez, força a necessidade de capturar uma linha de cada vez.
Então, para construir o var , uma linha nova deve ser adicionada (à medida que a leitura é removida).

#!/bin/sh

nl='
'

read_heredoc() {
    unset var
    while IFS="$nl" read -r line; do
        var="$var$line$nl"
    done <<\HEREDOC

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ' _ \| | | | '_ \| |/ _' |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \ 
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/ 
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___| 
             __/ | | 
            |___/|_| 



HEREDOC

}

read_heredoc
printf '%s' "$var"

Isso funciona (e foi testado) em todos os shells razoáveis.

    
por 29.01.2017 / 03:39
1

Para suportar novas linhas, combinei a resposta de @MichaelHomer e minha solução original. Eu não usei as soluções sugeridas do link que @EliahKagan notou porque a primeira usa strings mágicas e as duas últimas não eram compatíveis com POSIX.

#!/bin/sh

NEWLINE="
"

read_heredoc() {
  read_heredoc_result=""
  while IFS="${NEWLINE}" read -r read_heredoc_line; do
    read_heredoc_result="${read_heredoc_result}${read_heredoc_line}${NEWLINE}"
  done
  eval $1'=${read_heredoc_result}'
}

read_heredoc heredoc_str <<'HEREDOC'

                        _                            _ _
                       | |                          | (_)
  _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | |_ _ __   ___
 | '_ ' _ \| | | | '_ \| |/ _' |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
 | | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
 |_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
             __/ | |
            |___/|_|




HEREDOC

echo "${heredoc_str}"
    
por 29.01.2017 / 05:44
0

Uso inútil de gato (quote \ and '):

myplaceonline="
                       _                            _ _            
 _ __ ___  _   _ _ __ | | __ _  ___ ___  ___  _ __ | (_)_ __   ___ 
| '_ \' _ \| | | | '_ \| |/ _\' |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_|  __/ (_) | | | | | | | | |  __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
       |___/|_
"

Ou sem citar:

myplaceonline="$(figlet myplaceonline)"
    
por 23.02.2017 / 17:10