Nos 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 o IFS como 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:
lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)
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 todas as chamadas.
Se você deseja iterar sobre linhas não vazias, faça o mesmo:
IFS='
'
set -f
for line in $lines
do
printf '[%s]\n' $line
done
Manipular o IFS dessa maneira é uma técnica frequentemente ignorada, 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 a remoção deliberada do 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
, convém incluir -vf scale=$scale
apenas quando a variável scale
estiver definida como algo não vazio. Normalmente, você pode conseguir isso, ${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 concluída, o espaço entre -vf
e scale=
não será usado como um separador de palavras e ffmpeg
será passado -vf scale=$scale
como um único argumento, que não vai entender.
Para corrigir isso, você quer necessidade de certificar-se de IFS foi criado mais normalmente antes de fazer a ${scale}
expansão, ou fazer duas expansões: ${scale:+-vf} ${scale:+scale=$scale}
. A palavra divisão que o shell faz no processo de análise inicial das linhas de comando, em oposição à divisão durante a fase de expansão do processamento dessas linhas de comando, não depende do IFS.
Outra coisa que poderia valer a pena, se você fizer esse tipo de coisa, seria criar duas variáveis globais do shell para conter apenas uma guia e apenas uma nova linha:
t=' '
n='
'
Dessa forma, você pode apenas incluir $t
e $n
em expansões nas quais você precisa de guias e novas linhas, em vez de colocar todo o seu código no espaço em branco entre aspas. Se você preferir evitar o espaço em branco citado em um shell POSIX que não tem outro mecanismo para fazê-lo, printf
pode ajudar, embora você precise de um pouco de mexer para contornar a remoção de novas linhas à direita nas expansões de comando:
nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}
Às vezes, definir o 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:
while IFS=$t read -r path scale
do
ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt
Nesse caso, o read
built-in vê o IFS definido como apenas uma guia, para não dividir a linha de entrada que lê nos espaços também. Mas IFS=$t set -- $lines
não funciona: o shell se expande à $lines
medida que constrói os set
argumentos do builtin antes de executar o comando, portanto, a configuração temporária do IFS de uma maneira que se aplica somente durante a execução do próprio buildin chega tarde demais. É por isso que os trechos de código que eu dei acima de tudo definem o IFS em uma etapa separada e por que eles precisam lidar com a questão de preservá-lo.
wc -l
é exatamente equivalente ao original:<<<$foo
adiciona uma nova linha ao valor de$foo
(mesmo que$foo
estivesse vazio). Explico na minha resposta por que isso pode não ter sido o que se queria, mas foi o que foi perguntado.