Preservar líder espaço em branco durante a leitura >> escrevendo um arquivo linha por linha em bash
-
22-07-2019 - |
Pergunta
Eu estou tentando fazer um loop através de um diretório de arquivos de texto e combiná-los em um único documento. Isso funciona muito bem, mas os arquivos de texto contêm trechos de código, e toda minha formatação está sendo recolhido para a esquerda. Tudo levando espaços em branco em uma linha é despojado.
#!/bin/sh
OUTPUT="../best_practices.textile"
FILES="../best-practices/*.textile"
for f in "$FILES"
do
echo "Processing $f file..."
echo "">$OUTPUT
cat $f | while read line; do
echo "$line">>$OUTPUT
done
echo >>$OUTPUT
echo >>$OUTPUT
done
Eu estou reconhecidamente um noob bash, mas depois de procurar alto e baixo eu não poderia encontrar uma solução adequada. Aparentemente BASH odeia o espaço em branco levando em geral.
Solução
Em vez de:
cat $f | while read line; do
echo "$line">>$OUTPUT
done
Faça o seguinte:
cat $f >>$OUTPUT
(Se há uma razão que você precisa fazer as coisas linha por linha que seria bom para incluir que na pergunta.)
Outras dicas
Como os outros têm para fora pontas, usando gato ou awk em vez de um loop de leitura-eco é uma maneira muito melhor de fazer isso - evita o problema-aparar espaço em branco (e um par de outros que você não ter tropeçado em cima), corre mais rápido, e pelo menos com o gato, é simplesmente um código mais limpo. No entanto, eu gostaria de tomar uma facada no sentido de conseguir o loop de leitura-echo para a direita trabalho.
Em primeiro lugar, o problema-aparar espaço em branco: o comando ler automaticamente cortado esquerda e à direita espaço em branco; isto pode ser corrigido alterando a sua definição de espaço em branco ajustando a variável de IFS para branco. Além disso, leitura assume que uma barra invertida no final da linha significa que a próxima linha é uma continuação, e devem ser unidas em conjunto com este um; para corrigir isso, use a sua bandeira -r (raw). O terceiro problema aqui é que muitas implementações de eco interpretar sequências de escape na cadeia (por exemplo, podem transformar \ n em uma nova linha real); para corrigir isso, use printf vez. Finalmente, assim como uma regra de higiene scripting geral, você não deve usar gato quando você realmente não precisa; usar redirecionamento de entrada em seu lugar. Com essas mudanças, o loop olhares internos como este:
while IFS='' read -r line; do
printf "%s\n" "$line">>$OUTPUT
done <$f
... também há um par de outros problemas com o roteiro envolvente: a linha que tenta definir os arquivos como a lista de arquivos .textile disponíveis tem aspas em torno dele, o que significa que nunca se expandiu em uma lista real de arquivos . A melhor maneira de fazer isso é usar um array:
FILES=(../best-practices/*.textile)
...
for f in "${FILES[@]}"
(e todas as ocorrências de $ f deve estar em aspas no caso de qualquer um dos nomes de arquivos têm espaços ou outros personagens engraçados em si - deve realmente fazer isso com $ OUTPUT bem, embora desde que é definido no script é realmente seguro para deixar de fora.)
Finalmente, há uma echo "">$OUTPUT
perto do topo do laço-over-arquivos que vai apagar o arquivo de saída de cada vez através de (ou seja, no final, ele contém apenas o último arquivo .textile); isso precisa ser movido para antes do loop. Eu não tenho certeza se a intenção aqui foi a de colocar uma linha em branco no início do arquivo, ou três linhas em branco entre arquivos (e uma no início e dois no final), então eu não sei exatamente o que a substituição apropriado é. De qualquer forma, aqui está o que eu posso com após a fixação todos estes problemas:
#!/bin/sh
OUTPUT="../best_practices.textile"
FILES=(../best-practices/*.textile)
: >"$OUTPUT"
for f in "${FILES[@]}"
do
echo "Processing $f file..."
echo >>"$OUTPUT"
while IFS='' read -r line; do
printf "%s\n" "$line">>"$OUTPUT"
done <"$f"
echo >>"$OUTPUT"
echo >>"$OUTPUT"
done
que é uma maneira excessivamente caros de combinar arquivos.
cat ../best-practices/*.textile > ../best_practices.textile
Se você quiser adicionar um em branco (nova linha) para cada arquivo como você concatenar, use awk
awk 'FNR==1{print "">"out.txt"}{print > "out.txt" }' *.textile
ou
awk 'FNR==1{print ""}{print}' file* > out.txt
Isto permite-lhe novas linhas Intercale entre cada arquivo de entrada como você tem feito no seu script original:
for f in $FILES; do echo -ne '\n\n' | cat "$f" -; done > $OUTPUT
Note que $FILES
é não cotadas para este ao trabalho (caso contrário, as novas linhas extras aparecer apenas uma vez no final de toda a saída), mas $f
devem ser arredondadas para espaços protegerá em nomes de arquivos, se eles existirem.
A resposta correta, imo, é este , reproduzido abaixo:
while IFS= read line; do
check=${line:0:1}
done < file.txt
Note que ele vai cuidar de situações em que a entrada é canalizada de outro comando, e não apenas de um arquivo real.
Note que você também pode simplificar o redirecionamento como mostrado abaixo.
#!/bin/bash
OUTPUT="../best_practices.textile"
FILES="../best-practices/*.textile"
for f in "$FILES"
do
echo "Processing $f file..."
{
echo
while IFS= read line; do
echo "$line"
done < $f
echo
echo;
} > $OUTPUT
done