leia -a matriz -d '\ n' <foo, código de saída 1

8

Se eu tentar executar

read -a fooArr -d '\n' < bar

o código de saída é 1 - mesmo que ele realize o que eu quero; coloque cada linha de barem um elemento da matriz fooArr(usando o bash 4.2.37).

Alguém pode explicar por que isso está acontecendo


Eu encontrei outras maneiras de resolver isso, como as abaixo, então não é isso que estou pedindo.

for ((i=1;; i++)); do
    read "fooArr$i" || break;
done < bar

ou

mapfile -t fooArr < bar
RasmusWL
fonte

Respostas:

14

O que precisa ser explicado é que o comando parecia funcionar, não seu código de saída

'\n'tem dois caracteres: uma barra invertida \e uma letra n. O que você pensou que precisava era $'\n', que é um avanço de linha (mas isso também não seria correto, veja abaixo).

A -dopção faz isso:

  -d delim  continue until the first character of DELIM is read, rather
            than newline

Portanto, sem essa opção, readseria possível ler uma nova linha, dividir a linha em palavras usando os caracteres $IFScomo separadores e colocar as palavras na matriz. Se você especificou -d $'\n', definindo o delimitador de linha como uma nova linha, ele faria exatamente a mesma coisa . Configuração -d '\n'significa que ele lerá a primeira barra invertida (mas, mais uma vez, veja abaixo), que é o primeiro caractere delim. Como não há barra invertida em seu arquivo, o readfinal será no final do arquivo e:

Exit Status:
The return code is zero, unless end-of-file is encountered, read times out,
or an invalid file descriptor is supplied as the argument to -u.

É por isso que o código de saída é 1.

Pelo fato de você acreditar que o comando funcionou, podemos concluir que não há espaços no arquivo, para que read, depois de ler o arquivo inteiro na esperança fútil de encontrar uma barra invertida, o divida por espaço em branco (o valor padrão de $IFS), incluindo novas linhas. Portanto, cada linha (ou cada palavra, se uma linha contiver mais de uma palavra) é armazenada na matriz.

O misterioso caso da barra invertida roubada

Agora, como eu sabia que o arquivo não continha barras invertidas? Porque você não forneceu a -rbandeira para read:

  -r                do not allow backslashes to escape any characters

Portanto, se você tivesse barras invertidas no arquivo, elas teriam sido removidas, a menos que você tivesse duas delas seguidas. E, é claro, existem evidências de que readhavia um código de saída 1, o que demonstra que ele não encontrou uma barra invertida; portanto, não havia dois deles seguidos.

Aprendizado

Bash não seria uma festa, se não houvesse truques escondidos atrás de quase todos os comandos, e readnão é exceção. Aqui estão alguns:

  1. A menos que você especifique -r, readinterpretará as seqüências de escape de barra invertida. A menos que seja realmente o que você deseja (o que ocasionalmente é, mas apenas ocasionalmente), lembre-se de especificar -rpara evitar que os caracteres desapareçam, no caso raro de haver barras invertidas na entrada.

  2. O fato de readretornar um código de saída 1 não significa que ele falhou. Pode ter sido bem-sucedido, exceto para encontrar o terminador de linha. Portanto, tenha cuidado com um loop como este: while read -r LINE; do something with LINE; done porque falhará do somethingcom a última linha no caso raro de que a última linha não tenha uma nova linha no final.

  3. read -r LINE preserva barras invertidas, mas não preserva os espaços em branco à esquerda ou à direita.

rici
fonte
Obrigado! Eu pensei que eu tinha tentado a $ abordagem '\ n', mas acho que não: / bom saber sobre o -r embora
RasmusWL
2
"o raro caso de a última linha não ter uma nova linha" não me parece uma razão convincente para aconselhar "nunca use while read". Caso contrário, resposta fantástica.
Glenn Jackman
@glennjackman: Você pode usar while readdesde que não tenha nada dentro do loop. Caso contrário, é um bug esperando para morder você - e acredite, fui mordido.
rici
2
A última linha sempre tem uma nova linha. É assim que uma linha é definida: termina com uma nova linha. Se um arquivo não vazio não terminar com uma nova linha, não será um arquivo de texto e você não poderá usar ferramentas de processamento de texto, como o utilitário shell read.
Gilles 'SO- stop be evil'
2
@glennjackman: Eu, eu itero através de arquivos em colunas com awk, principalmente. Para iterar linhas inteiras onde o arquivo não é muito grande, mapfileé bem legal. Para um hack rápido e sujo, usarei o loop while proibido, mas parei de colocar isso nos scripts de produção. YMMV e eu suavizamos o aviso na resposta.
rici
4

Esse é o comportamento esperado:

O código de retorno é zero, a menos que seja encontrado o final do arquivo, [...]

start cmd:> echo a b c | { read -a testarray; echo $?; }
0

start cmd:> echo -n a b c | { read -a testarray; echo $?; }
1
Hauke ​​Laging
fonte