Verifique se uma variável shell tem mais de uma linha usando built-ins?

3

Eu posso fazer isso chamando o utilitário externo sed (para um não-vazio $myvar ) assim:

if [ "$(printf %s "$myvar" | sed -n '$=')" -eq 1 ]; then
  echo "Your variable has only one line, proceeding"
else
  echo "Error condition, variable must have only one line"
fi

Existe uma maneira de fazer isso usando apenas bash builtins?

Melhor ainda, existe uma maneira de fazer isso de uma maneira especificada pelo POSIX, sem chamar utilitários externos?

(Esta questão é de curiosidade e encontrar melhores maneiras de fazer as coisas; o código acima faz funcionar. Eu estou querendo saber se existe uma maneira mais limpa / mais rápida.)

    
por Wildcard 16.04.2016 / 05:38

5 respostas

10

A maneira POSIX:

NL='
'
case $myvar in
  *"$NL"*) echo more than one line ;;
        *) echo one line ;;
esac

Isso também funciona em shells pré-POSIX semelhantes a Bourne, também.

    
por 16.04.2016 / 05:45
5

Os snippets a seguir funcionam em bash (com e sem a opção -posix ):

#!/bin/bash
#!/bin/bash -posix
version_1 () { [[ "$myvar" = *$'\n'* ]]; }
version_2 () {
  local newlines="${myvar//[^$'\n']/}"
  [[ "${#newlines}" -eq 1 ]]
}
for test in version_1 version_2; do
  if $test; then echo many lines; else echo one line; fi
done
    
por 16.04.2016 / 12:03
4

Existem várias opções (bash primeiro, o POSIX está abaixo).
O código dentro de cada função pode ser facilmente usado fora.

#!/bin/bash

nl=$'\n'

aregex   (){ [[ $a =~        $nl     ]]; }
apattern (){ [[ $a ==       *$nl*    ]]; }
acut     (){ [[ $a != "${a%%"$nl"*}" ]]; }
areplace (){ [[ $a != "${a//"$nl"/}" ]]; }
acase    (){ case $a in (*$nl*) true;; (*) false;; esac; }
aifs     ()( IFS="$nl"
             set -f; set -- x${a}x;
             (( $# > 1 ));
           )
aread    (){ IFS="$nl" read -rd '' -a b <<<"x${a}x";
             (( "${#b[@]}" > 1 )); }

Cada função é uma opção. Cada função sairá com um valor de retorno de 0 se houver apenas uma linha e um valor de retorno de 1 se a variável $a tiver mais de uma nova linha (mais de um $'\n' ).

Depois que uma função é executada, isso imprimirá a linha necessária:

out=''; "$function" && out="more than "
printf "%9s   = %sone line\n" "$1" "$out"

Executando todas as opções:

a='ab'"$nl"'cd' ; alltests
a='ab  cd'      ; alltests

Dá esta saída:

   aregex   = more than one line
 apattern   = more than one line
     acut   = more than one line
 areplace   = more than one line
    acase   = more than one line
     aifs   = more than one line
    aread   = more than one line

   aregex   = one line
 apattern   = one line
     acut   = one line
 areplace   = one line
    acase   = one line
     aifs   = one line
    aread   = one line  

POSIX

As seguintes opções falham no POSIX por vários motivos:

  • aregex: Não há operadores regex =~ no POSIX.
  • apattern: não há nenhum operador == no POSIX.
  • areplace: a expansão de parâmetro não possui a opção ${ / / } .

Quatro podem ser traduzidas com segurança para POSIX:

  • posixcut: Provavelmente a melhor solução.
  • posixcase: Uma solução comum para shells POSIX.
  • posixifs: Executado dentro de um sub-shell para poder definir set -f .
  • posixread: read não tem -d ou -a , mas pode ser adaptado.
#!/bin/dash

nl='
'

posixcut (){ [ "${a}" != "${a%%"$nl"*}" ] ; }
posixcase(){ case $a in (*$nl*) true;; (*) false;; esac; }
posixifs ()( IFS="$nl";
             set -f; set -- x${a}x;
             [ "$#" -gt 1 ];
           )
posixread(){ local b=0;
             while IFS=$nl read -r _; do
                 b=$((b+1));
                 [ $b -gt 1 ] && break;
             done <<-_EOT_
x${a}x
_EOT_
             [ $b -gt 1 ];
           }

Nota : Sim, local não é estritamente POSIX, mas é muito bem suportado .
O local para b , pode ser removido com segurança (verifique o uso global de $b ).

Bourne (concha de 1977).

Se você precisar de código para o shell Bourne 1977 original, use este:

posixcase() { case $a in *$nl*) true;; *) false;; esac; }
posixifs () ( IFS="$nl"
              set -f; set -- x${a}x;
              [ "$#" -gt 1 ];
            )
bourneread()( b=0                ### A helper function.
              while :; do
                  if IFS=$nl read c && [ $b -le 1 ]; then
                      b='expr $b + 1'
                  else
                      echo "$b"
                      break
                  fi    
              done <<-_EOT_
x${a}x
_EOT_
             )
posixread   (){ [ 'bourneread' -gt 1 ]; }

A expansão ${a%% } usada no posixcut não funcionará em Bourne. O posixifs precisa ser dividido em duas partes para funcionar em Bourne.
A opção de leitura precisa de mudanças de prefeito, mas funciona corretamente.
Sua função é posixread, o bourneread é uma função auxiliar necessária.

Todo o código do Bourne foi testado para funcionar corretamente com o shell da herança .

    
por 17.04.2016 / 10:11
1

Aqui está uma maneira que deve funcionar com todas as shells da sintaxe Bourne / POSIX e usa somente builtins:

if (set -f ; IFS=$'\n'; set -- x${myvar}x ; [ $# = 1 ]) ; then
  echo "Your variable has only one line, proceeding"
else
  echo "Error condition, variable must have exactly one line"
fi

Se o seu shell não suporta IFS=$'\n' (como dash 0.5.7), você pode usar:

IFS="
"
    
por 17.04.2016 / 10:40
1

Eu recomendaria usar a expansão de parâmetros Bash da seguinte forma:

n="${myvar//[^\n]}"; if [ ${#n} -eq 1 ]; then
  echo "Your variable has only one line, proceeding"
else
  echo "Error condition, variable must have only one line"
fi

Amostras de teste:

myvar="xxx"
myvar="xx\nxx"
myvar="xx\nx\nx"
    
por 29.11.2018 / 19:11