Estou tendo um problema com um script bash que está ecoando a saída em uma ordem inesperada. O roteiro é o seguinte. O problema está na saída das linhas 30-32.
1 IFS=$'\n'
2 i=1
3 bluered=""
4 blueyellow=""
5 redyellow=""
6 all=""
7 while [ $i -le `cat sorted.csv | wc -l` ]
8 do
9 for j in {0..2}
10 do
11 # cat sorted.csv | head -$i | tail -1 | awk -F',' '{print $1}'
12 declare "`cat sorted.csv | head -$i | tail -1 | awk -F',' '{print $1}'`=`cat sorted.csv | head -$i | tail -1 | awk -F',' '{print $5}'`"
13 i=$((i+1))
14 done
15
16 if [[ ${blue} == ${red} ]]; then bluered=1; else bluered=0; fi
17 if [[ ${blue} == ${yellow} ]]; then blueyellow=1; else blueyellow=0; fi
18 if [[ ${red} == ${yellow} ]]; then redyellow=1; else redyellow=0; fi
19 if [[ ${blue} == ${red} ]] && [[ ${red} == ${yellow} ]]; then all=1; else all=0; fi
20
21 echo "`cat sorted.csv | head -$((i-3)) | tail -1`"
22 echo ",$all,$bluered,$blueyellow,$redyellow"
23 echo "`cat sorted.csv | head -$((i-2)) | tail -1`"
24 echo ",$all,$bluered,$blueyellow,$redyellow"
25 echo "`cat sorted.csv | head -$((i-1)) | tail -1`"
26 echo ",""$all"",""$bluered"",""$blueyellow"",""$redyellow"
27
28
29
30 echo "`cat sorted.csv | head -$((i-3)) | tail -1`,$all,$bluered,$blueyellow,$redyellow"
31 echo "`cat sorted.csv | head -$((i-2)) | tail -1`"",$all,$bluered,$blueyellow,$redyellow"
32 echo "`cat sorted.csv | head -$((i-1)) | tail -1`"",""$all"",""$bluered"",""$blueyellow"",""$redyellow"
33 done
As linhas 30-32 têm uma formatação de aspas duplas ligeiramente diferente, pois eu estava tentando coisas diferentes para que funcionasse corretamente. As linhas 21-26 nada mais são do que as linhas 30-32 decompostas em 2 partes (ou seja, a linha 21-22 é igual à linha 30).
Com base no arquivo de entrada, "sorted.csv", a saída correta das linhas 30-32 (para as primeiras 3 linhas do arquivo de entrada) deve ser:
blue,1,WCC131035882,0,e89d89d7ca7c502ca8d3b2e0d7c4980dba346a63d57a437d8f1428065fb83e9f,0,0,0,1
red,1,Z292V5DB,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68,0,0,0,2
yellow,1,Z292V94K,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68,0,0,0,1
mas a saída real é:
,0,0,0,1CC131035882,0,e89d89d7ca7c502ca8d3b2e0d7c4980dba346a63d57a437d8f1428065fb83e9f #(line 30 output)
,0,0,0,192V5DB,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68 #(line 31 output)
,0,0,0,1,Z292V94K,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68 #(line 32 output)
As linhas 21 a 26 retornam a seguinte saída:
blue,1,WCC131035882,0,e89d89d7ca7c502ca8d3b2e0d7c4980dba346a63d57a437d8f1428065fb83e9f #(line 21 output)
,0,0,0,1 #(line 22 output)
red,1,Z292V5DB,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68 #(line 23output)
,0,0,0,1 #(line 24 output)
yellow,1,Z292V94K,0,68a4917c878f1b26e370264097f476840aa995dc6b8d6d2e552a78a6bdd77c68 #(line 25)
,0,0,0,1 #(line 26 output)
Resumindo, quero concatenar a saída das linhas 21-22, 23-24 e 25-26 usando 3 comandos de linha única, como as linhas 30-32 (mas com a sintaxe correta). Observe que as linhas 21-26 estão incluídas apenas em o script para demonstrar que as duas partes da linha 30 (31 ou 32) estão funcionando corretamente quando separadas em duas linhas. Atualmente, a linha 30 concatena efetivamente a saída da linha 22 e depois da linha 21 em vez de 21 e depois 22. No entanto, ao fazer essa concatenação reversa, ela também trunca os primeiros 8 caracteres da saída da linha 21 (observe, a saída da linha 22 tem exatamente 8 caracteres).
Como escrevo corretamente as linhas 30-32 para que criem a saída desejada?
Agradecemos antecipadamente por sua ajuda.
Mais perspicácia do que um "uso" lacônico
dos2unix
.Aparentemente
sorted.csv
usa terminações de linha CR+LF enquanto deveria usar apenas LF .Quando você usa
`something`
avanços de linha (LF) no final da saídasomething
, são removidos, mas não retornos de carro (CR). No seu casotext+CR+LF
tornou -setext+CR
. Se for uma única entrada paraecho
, a ferramenta adiciona uma nova linha como de costume e, novamente, você tem CR+LF no final. Ao imprimir no console, este CR não muda nada.Mas, no caso do
echo "`foo`bar"
caractere CR retornado porfoo
ficar no meio da string resultante, qualquer coisa que vier a seguir será impressa na borda esquerda do console, sobrescrevendo a parte anterior.A solução é usar
dos2unix sorted.csv
, como você já observou .Mas há mais:
cat
$(stuff)
e`stuff`
?printf
melhor do queecho
?""
(por exemplo, na linha 32) não fazem nada. Eles estão fechando+abrindo, não abrindo+fechando. Eles apenas ofuscam o código. Eu entendo que eles são "experimentais" e estou dizendo claramente que eles não mudam nada.E talvez
O que há de errado com
echo $(stuff)
ouecho `stuff`
?Admito que esta objeção é questionável aqui.
"`foo`bar"
é útil retirar qualquer LF à direita da saída defoo
e concatená-lo combar
; no seu caso é importante. Então a sintaxe comoecho "`foo`"
foi usada para demonstrar a diferença relevante no comportamento, você afirmou isso explicitamente.dos2unix
certamente não responde à pergunta, que é [ênfase minha]:Uma linha 32 escrita corretamente usaria pelo menos
printf
, nocat
. Crases e aspas excessivas podem permanecer, embora o código seja mais legível com$( … )
citações razoáveis. Você também pode considerar separar o formato dos dados, é fácil comprintf
:Além disso, existem soluções ruins em outros lugares:
while [ $i -le `cat sorted.csv | wc -l` ]
(além decat
e backticks). Não há necessidade de obter o número de linhas a cada iteração.wc -l sorted.csv
deve ser executado uma vez antes do loop, seu resultado armazenado em uma variável (a menos que você espere que o número de linhas mude durante a execução, mas acho que não; a lógica do script faz mais sentido se o número não mudar ).Você lê o arquivo de novo e de novo. Quanto mais linhas houver, mais vezes você reabrirá, lerá desde o início e escolherá uma única linha de cada vez. O fluxo deve ser reprojetado para que o arquivo seja analisado linha por linha, possivelmente sem o anterior
wc -l
, possivelmente de maneira semelhante a um canal (ou seja, aberto apenas uma vez, lido apenas uma vez sem pular para trás). Já que você usa odeclare
builtin,while IFS= read -r … ; done <sorted.csv
provavelmente é obrigatório (talvez com preliminarawk
, por exemplo<sorted.csv awk … | while IFS= read -r …
). Você poderead
três vezes para três variáveis diferentes e, em seguida, executar operações; então leia as próximas três linhas.read
em si é ineficiente, mas é mais elegante do que reabrir o arquivo várias vezes. Observe que seu script se torna menos eficiente a cada linha adicional no arquivo; se o arquivo for grande o suficiente para que a ineficiênciaread
se manifeste, sua abordagem provavelmente terá um desempenho ainda pior.Toda essa mudança, se você aceitar, não será trivial.
O nome do arquivo não deve ser codificado em tantos lugares. Ler o arquivo apenas uma vez reduziria naturalmente esse problema. Mesmo assim é bom começar
input_file="sorted.csv"
e usar"$input_file"
sempre que precisar. Se você decidir passar o caminho do arquivo como um argumento de linha de comando, será tão fácil quanto digitarinput_file="$1"
.Por que não há shebang?
Considerando o quadro geral, as linhas 30-32 mais corretas seriam como neste trecho:
Provavelmente o único
awk
trabalho como filtro pode fazer isso; o arquivo seria canalizado para ele. Emawk
você pode armazenar informações em variáveis também, para usá-las posteriormente. Condicionais também estão disponíveis. Algo como este exemplo mal testado :(Nota:
awk
testei com is de fatomawk
). Salve-o, torne-o executável, canalize seusorted.csv
para ele (por exemplo,<sorted.csv ./the_script
).O comentário sobre retornos de carro estar no arquivo foi direto. Depois de executar o arquivo de entrada pensado dox2unix, o script foi executado conforme o esperado. Obrigado, Gordon.