web-dev-qa-db-ja.com

bashスクリプト:エコー出力が文字化けし、順序が間違っている

予期しない順序で出力をエコーするbashスクリプトに問題があります。スクリプトは次のとおりです。問題は、行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

30〜32行目は、正しく機能させるためにさまざまなことを試みていたため、二重引用符のフォーマットがわずかに異なります。 21〜26行目は、30〜32行目を2つの部分に分解したものにすぎません(つまり、21〜22行目は30行目と同じです)。

入力ファイル「sorted.csv」に基づいて、30〜32行目の正しい出力(入力ファイルの最初の3行)は次のようになります。

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

ただし、実際の出力は次のとおりです。

,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)

21〜26行目は、次の出力を返します。

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)

要するに、行30-32のような3つの単一行コマンドを使用して行21-22、23-24、および25-26からの出力を連結したい(ただし構文は正しい)。注:行21-26はにのみ含まれています。 30行目の2つの部分(31または32)が2行に分割されたときに正しく機能していることを示すスクリプト。現在、30行目は、21行目から22行目ではなく、22行目から21行目までの出力を効果的に連結しています。ただし、この逆連結を行うと、21行目の出力の最初の8文字も切り捨てられます(出力に注意してください)。 22行目のは正確に8文字です)。

30〜32行目を正しく記述して、目的の出力を作成するにはどうすればよいですか?

よろしくお願いします。

1
Brian

簡潔な「use _dos2unix_」よりも洞察力があります。

どうやら_sorted.csv_はCR + LF行末を使用しますが、 LFのみ を使用する必要があります。

_`something`_を使用すると、somethingの出力の最後で改行(LF)が削除されますが、キャリッジリターン(CR)は削除されません。あなたの場合、_text+CR+LF_は_text+CR_になりました。 echoへの唯一の入力である場合、ツールは通常どおり改行を追加し、最後にCR + LFがあります。コンソールに印刷している間、このCRは何も変更しません。

ただし、_echo "`foo`bar"_の場合、fooによって返されるCR文字は結果の文字列の中央に留まるため、後続の文字はコンソールの左端から出力され、前の部分が上書きされます。

解決策は、_dos2unix sorted.csv_を使用することです すでに述べたように


しかし、それだけではありません。

そしてたぶん

  • echo $(stuff)または_echo `stuff`_の何が問題になっていますか?

    私はこの異議がここで疑わしいことを認めます。 _"`foo`bar"_は、fooの出力から末尾のLFを取り除き、それをbarと連結するのに役立ちます。あなたの場合は重要です。次に、構文_echo "`foo`"_が動作の関連する違いを示すために使用されたように、あなたはこれを明示的に述べました。


_dos2unix_は確かに質問に答えません。それは[私の強調]です:

30〜32行目を正しく書き込んで、目的の出力を作成するにはどうすればよいですか?

正しく書かれた32行目は、少なくともprintfを使用し、catは使用しません。コードは$( … )と適切な引用符を使用すると読みやすくなりますが、バッククォートと過度の引用符が残る場合があります。フォーマットをデータから分離することも検討できます。printfを使用すると簡単です。

_printf '%s,%s,%s,%s,%s\n' "$( <sorted.csv head -$((i-1)) | tail -1 )" "$all" "$bluered" "$blueyellow" "$redyellow"
_

さらに、他の場所には不十分な解決策があります。

  • _while [ $i -le `cat sorted.csv | wc -l` ]_(catとバッククォートを除く)。反復ごとに行数を取得する必要はありません。 _wc -l sorted.csv_は、ループの前に1回実行する必要があり、その結果は変数に格納されます(実行中に行数が変わると予想しない限り、そうではないと思います。スクリプトのロジックは、番号は変わりません)。
  • あなたは何度も何度もファイルを新たに読んだ。行数が多いほど、再度開いて最初から読み、毎回単一行を選択する回数が増えます。フローを再設計して、ファイルが1行ずつ、おそらく前の_wc -l_なしで、おそらくパイプのように解析されるようにする必要があります(つまり、1回だけ開いて、スキップせずに1回だけ読み取ります)。 declareビルトインを使用しているので、_while IFS= read -r … ; done <sorted.csv_はおそらく必須です(おそらく予備のawk、たとえば_<sorted.csv awk … | while IFS= read -r …_)。 readを3回実行して、3つの異なる変数を作成してから、操作を実行できます。次に、次の3行を読みます。 read自体は非効率的ですが、ファイルを何度も再度開くよりもエレガントです。ファイルに行を追加するたびに、スクリプトの効率が低下することに注意してください。ファイルがreadの非効率性を明らかにするのに十分な大きさである場合、アプローチのパフォーマンスはさらに低下する可能性があります。

    しかし、この全体的な変更は、あなたがそれを行えば、些細なことではありません。

  • ファイルの名前は、それほど多くの場所でハードコーディングしないでください。ファイルを1回だけ読み取ると、当然この問題は軽減されます。それでも、_input_file="sorted.csv"_から始めて、必要に応じて_"$input_file"_を使用することをお勧めします。ファイルパスをコマンドライン引数として渡すことにした場合は、_input_file="$1"_と入力するのと同じくらい簡単です。

  • なぜシバンがないのですか?

全体像を考慮すると、30〜32行目は次のようになります。

_#!/bin/bash
input_file="sorted.csv
…
while IFS= read -r pre_previous_line && IFS= read -r previous_line && IFS= read -r current_line; do
   …
   # useful bashism instead of    echo "$previous_line" | some_command
   some_command <<< "$previous_line"
   …
   # printf will reuse the format if there are more arguments than the format needs
   # so this one line will be enough for your three
   # (split for readability, it's still one line for the Shell)
   printf '%s,%s,%s,%s,%s\n' \
      "$pre_previous_line" "$all" "$bluered" "$blueyellow" "$redyellow" \
      "$previous_line"     "$all" "$bluered" "$blueyellow" "$redyellow" \
      "$current_line"      "$all" "$bluered" "$blueyellow" "$redyellow"
   …
done <"$input_file"
_

おそらく、フィルターとして機能する唯一のawkがこれを行うことができます。ファイルはそれにパイプされます。 awkでは、後で使用するために、変数に情報を格納することもできます。条件付きもご利用いただけます。このようなものテストが不十分の例:

_#!/usr/bin/awk -f
BEGIN { FS="," }
{
if ($1 == "blue") blue=$5
if ($1 == "red") red=$5
if ($1 == "yellow") yellow=$5
if (NR%3 == 1) prepre=$0
if (NR%3 == 2) pre=$0
if (NR%3 == 0)
   {
   bluered=($blue == $red)
   blueyellow=($blue == $yellow)
   redyellow=($red == $yellow)
   all= bluered * blueyellow
   suffix=","all","bluered","blueyellow","redyellow
   print prepre suffix
   print pre    suffix
   print $0     suffix
   }
}
_

(注:私がテストしたawkは実際にはmawkです)。それを保存し、実行可能にし、_sorted.csv_をそれにパイプします(例:_<sorted.csv ./the_script_)。

2

ファイルにあるキャリッジリターンについてのコメントは死んでいた。 dox2unixと思われる入力ファイルを実行した後、スクリプトは期待どおりに実行されました。ありがとう、ゴードン。

0
Brian