Como ler a linha completa no loop 'for' com espaços

130

Estou tentando executar um forloop para o arquivo e quero exibir a linha inteira. Mas, em vez disso, está exibindo apenas a última palavra. Eu quero a linha completa.

for j in `cat ./file_wget_med`

do
echo $j

done

resultado após a execução:

Found.

Aqui estão os meus dados:

$ cat file_wget_med
2013-09-11 14:27:03 ERROR 404: Not Found.
user192118
fonte
relacionado: stackoverflow.com/questions/9084257/…
Ciro Santilli escreveu:

Respostas:

179

forO loop é dividido quando vê qualquer espaço em branco, como espaço, tabulação ou nova linha. Portanto, você deve usar o IFS (Internal Field Separator) :

IFS=$'\n'       # make newlines the only separator
for j in $(cat ./file_wget_med)    
do
    echo "$j"
done
# Note: IFS needs to be reset to default!
Radu Rădeanu
fonte
5
E tome cuidado com aspas simples no IFS, porque IFS = $ "\ n" também dividirá as seqüências de caracteres "nn". Eu achei o IFS mais confiável do que usar funções ou sintaxe mais complicadas.
Erm3nda
23
aparentemente é aconselhável unset IFSdepois, para que outros comandos não sejam influenciados nesse mesmo shell.
Boris Däppen
2
O que $significa isso IFS=$'\n'?
Ren
2
$faz quebras de linha funcionem dentro da string. Isso também deve ser feito com local IFS=$'\n'dentro de uma função.
21417 Andy Murray
11
@Renn que é $'...'conhecido " ANSI-C
Quoting
82

forloops divididos em qualquer espaço em branco (espaço, tabulação, nova linha) por padrão; a maneira mais fácil de trabalhar em uma linha de cada vez é usar um while readloop, que se divide em novas linhas:

while read i; do echo "$i"; done < ./file_wget_med

Eu esperaria que seu comando cuspisse uma palavra por linha (foi o que aconteceu quando eu testei com um arquivo próprio). Se algo mais estiver acontecendo, não sei ao certo o que poderia estar causando isso.

evilsoup
fonte
8
Esta é também uma boa alternativa para for i in `ls`; do echo $1; done, canalizando a saída para o comando while: ls|while read i; do echo $i; done. Isso funciona em nomes de arquivos / pastas com espaços, sem a necessidade de alterar variáveis ​​de ambiente. Resposta muito boa.
jishi
o tempo não lidar com a primeira e última linhas muito, não, bem como o loop com o IFS
grantbow
@jishi Como é projetado principalmente para exibição e responde ao tamanho da janela do terminal, lsnão é considerado seguro para uso com |(o operador do tubo). findé mais flexível, além de ser mais seguro para essa finalidade.
SeldomNeedy
3
Esta é uma ótima resposta, usei-a para transformar uma lista que possuía em entradas PLIST para um aplicativo IOS que estava desenvolvendo no mac OSX. I substituiu a entrada de arquivo canalizando a clipboad usando pbpaste ->pbpaste | while read t; do echo "<string>$t</string>"; done
Big Rico
3
ls -1pode ser usado com |a garantia de saída formatado linha um-por-
Andreys Scherbakov
19
#!/bin/bash
files=`find <subdir> -name '*'`
while read -r fname; do
    echo $fname
done <<< "$files"

Funcionamento verificado, não aquele que você provavelmente deseja, mas não é possível fazê-lo com elegância.

Mitchell Currie
fonte
11
uma string aqui! mais elegante de todas as outras soluções IMO
Eliran Malka
5

Aqui está uma pequena extensão da resposta de Mitchell Currie, que eu gosto por causa do pequeno escopo de efeitos colaterais, que evita a necessidade de definir uma variável:

#!/bin/bash
while read -r fname; do
    echo $fname
done <<< "`find <subdir>`"
Dandalf
fonte
11
Você poderia remover -name '*'e seria o mesmo, não?
wjandrea
1

Eu escreveria assim:

cat ./file_wget_med | while read -r j
do
    echo $j
done

pois requer menos alterações no script original (exceto pela solução em uso IFS, mas modifica o bashcomportamento não apenas para esta instrução de controle de loop).

mik
fonte
11
Tubo e catsão desnecessários aqui. whileloop aceita redirecionamento muito bem, e é por isso que geralmente é escrito como #while IFS= read -r line; do...done < input.txt
Sergiy Kolodyazhnyy
0

O Mapfile é uma maneira conveniente de ler linhas de um arquivo em uma matriz indexada, não tão portátil quanto a leitura, mas um pouco mais rápido. Ao usar o loop for, você evita criar um subshell.

#!/bin/bash

mapfile -t < file.txt

for line in "${MAPFILE[@]}"; do
    echo $line
done

Lembre-se de que ao usar pipelines, ele colocará o loop while em um subshell. Alterações dentro das variáveis ​​do loop while não serão propagadas para a parte externa do script.

Exemplo:

#!/bin/bash

a=0
printf %s\\n {0..5} | while read; do
  ((a++))
done
echo $a # 'a' will always be 0.

(Melhor solução):

#!/bin/bash

b=0
while read; do
  ((b++))
done < <(printf %s\\n {0..5})

echo $b # 'b' equal to 6 (works as expected).
bac0n
fonte
-1

Dandalf chegou bem perto de uma solução funcional, mas NUNCA se deve tentar atribuir o resultado de quantidades desconhecidas de entrada (ou seja find ~/.gdfuse -name '*') a variáveis! Ou, pelo menos, tentando fazer isso através de uma variável de matriz; se você insiste em ser tão preguiçoso! Então, aqui está a solução de Dandalf feita sem a manobra perigosa; e no total em uma linha

while read -r fname; do
  echo $fname;
done <<< `find ~/.gdfuse -name '*'
odoncaoa
fonte
Hey @odoncaoa, tentei fazer o comando find sem aspas duplas, mas faz com que todos os resultados apareçam como uma linha. Estou confuso sobre as "quantidades desconhecidas de entrada" com este comando e os perigos envolvidos. Você pode dar um exemplo dos perigos e como eles poderiam teoricamente acontecer? Eu realmente não desejo ser preguiçoso, apenas me sinto mais confortável em outras linguagens de programação.
Dandalf 03/01