Eu sei que posso fazer isso no Bash:
wc -l <<< "${string_variable}"
Basicamente, tudo que encontrei envolvia o <<<
operador Bash.
Mas no shell POSIX, <<<
é indefinido e não consegui encontrar uma abordagem alternativa por horas. Tenho certeza de que existe uma solução simples para isso, mas infelizmente não a encontrei até agora.
A resposta simples é que
wc -l <<< "${string_variable}"
é um atalho ksh/bash/zsh para arquivosprintf "%s\n" "${string_variable}" | wc -l
.Na verdade existem diferenças na forma
<<<
como um pipe funciona:<<<
cria um arquivo temporário que é passado como entrada para o comando, enquanto|
cria um pipe. Em bash e pdksh/mksh (mas não em ksh93 ou zsh), o comando no lado direito do pipe é executado em um subshell. Mas essas diferenças não importam neste caso em particular.Observe que em termos de contagem de linhas, isso pressupõe que a variável não esteja vazia e não termine com uma nova linha. Não terminar com uma nova linha é o caso quando a variável é o resultado de uma substituição de comando, então você obterá o resultado correto na maioria dos casos, mas obterá 1 para a string vazia.
Existem duas diferenças entre
var=$(somecommand); wc -l <<<"$var"
esomecommand | wc -l
: usar uma substituição de comando e uma variável temporária remove as linhas em branco no final, esquece se a última linha de saída terminou em uma nova linha ou não (sempre faz se o comando gerar um arquivo de texto não vazio válido) , e superconta em um se a saída estiver vazia. Se você deseja preservar o resultado e as linhas de contagem, pode fazê-lo anexando algum texto conhecido e removendo-o no final:Não está em conformidade com os embutidos do shell, usando utilitários externos como
grep
eawk
com opções compatíveis com POSIX,Fazendo com
grep
para corresponder ao início das linhasE com
awk
Note que algumas das ferramentas GNU, especialmente, GNU
grep
não respeitamPOSIXLY_CORRECT=1
a opção de rodar a versão POSIX da ferramenta. Nogrep
único comportamento afetado pela configuração da variável será a diferença no processamento da ordem dos sinalizadores de linha de comando. A partir da documentação (grep
manual GNU), parece queVeja Como usar POSIXLY_CORRECT no grep?
A string here
<<<
é praticamente uma versão de uma linha do here-document<<
. O primeiro não é um recurso padrão, mas o último é. Você pode usar<<
também neste caso. Estes devem ser equivalentes:No entanto, observe que ambos adicionam uma nova linha extra no final de
$somevar
, por exemplo, isso é impresso6
, mesmo que a variável tenha apenas cinco linhas:Com
printf
, você pode decidir se deseja a nova linha adicional ou não:Mas então, observe que
wc
apenas conta linhas completas (ou o número de caracteres de nova linha na string).grep -c ^
também deve contar o fragmento de linha final.(É claro que você também pode contar as linhas inteiramente no shell usando a
${var%...}
expansão para removê-las uma de cada vez em um loop ...)Nesses casos surpreendentemente frequentes em que o que você realmente precisa fazer é processar todas as linhas não vazias dentro de uma variável de alguma forma (incluindo contá-las), você pode definir IFS para apenas uma nova linha e usar o mecanismo de divisão de palavras do shell para quebrar as linhas não vazias separadas.
Por exemplo, aqui está uma pequena função shell que totaliza as linhas não vazias dentro de todos os argumentos fornecidos:
Parênteses, em vez de chaves, são usados aqui para formar o comando composto para o corpo da função. Isso faz com que a função seja executada em um subshell para que não polua a variável IFS do mundo externo e a configuração de expansão do nome do caminho em cada chamada.
Se você deseja iterar em linhas não vazias, pode fazê-lo da mesma forma:
Manipular o IFS dessa maneira é uma técnica frequentemente negligenciada, também útil para fazer coisas como analisar nomes de caminho que podem conter espaços de entrada colunar delimitada por tabulação. No entanto, você precisa estar ciente de que remover deliberadamente o caractere de espaço geralmente incluído na configuração padrão do IFS de space-tab-newline pode acabar desabilitando a divisão de palavras em locais onde você normalmente esperaria vê-lo.
Por exemplo, se você estiver usando variáveis para criar uma linha de comando complicada para algo como
ffmpeg
, talvez queira incluir-vf scale=$scale
apenas quando a variávelscale
estiver definida como algo não vazio. Normalmente você pode conseguir isso com${scale:+-vf scale=$scale}
, mas se o IFS não incluir seu caractere de espaço usual no momento em que essa expansão de parâmetro for feita, o espaço entre-vf
escale=
não será usado como um separador de palavras effmpeg
será passado-vf scale=$scale
como um único argumento, que não vai entender.Para corrigir isso, você precisa ter certeza de que o IFS foi definido mais normalmente antes de fazer a
${scale}
expansão ou fazer duas expansões:${scale:+-vf} ${scale:+scale=$scale}
. A divisão de palavras que o shell faz no processo de análise inicial das linhas de comando, em oposição à divisão que faz durante a fase de expansão do processamento dessas linhas de comando, não depende do IFS.Outra coisa que pode valer a pena se você for fazer esse tipo de coisa seria criar duas variáveis globais do shell para manter apenas uma guia e apenas uma nova linha:
Dessa forma, você pode apenas incluir
$t
e$n
em expansões onde precisar de guias e novas linhas, em vez de encher todo o seu código com espaços em branco citados. Se você preferir evitar completamente o espaço em branco citado em um shell POSIX que não possui outro mecanismo para fazer isso,printf
pode ajudar, embora você precise de um pouco de manipulação para contornar a remoção de novas linhas à direita em expansões de comando:Às vezes, definir IFS como se fosse uma variável de ambiente por comando funciona bem. Por exemplo, aqui está um loop que lê um nome de caminho que pode conter espaços e um fator de escala de cada linha de um arquivo de entrada delimitado por tabulação:
Nesse caso, o
read
builtin vê o IFS definido como apenas uma guia, portanto, também não dividirá a linha de entrada que lê em espaços. MasIFS=$t set -- $lines
não funciona: o shell se expande à$lines
medida que constrói osset
argumentos do builtin antes de executar o comando, então a configuração temporária do IFS de uma maneira que se aplica apenas durante a execução do próprio builtin vem tarde demais. É por isso que os trechos de código que dei acima definem o IFS em uma etapa separada e por que eles precisam lidar com a questão de preservá-lo.