2つの単語間の特定の文字をCR + LFに置き換える方法

2つの単語間の特定の文字をCR + LFに置き換える方法

どのサーバーに特定のファイルと所有権があるかを知らせるcsvファイルを生成したいと思います。これは私が得る生の出力です。

server01,server02,server03,owner,/etc/file1
server04,owner,/etc/file2
server05,server06,owner,/etc/file3

サーバー名間のカンマをWindows形式の改行(CF + LR)に変更し、その間に引用符を追加して、CSVに同じボックスにすべてのサーバーを表示させたいと思います。

希望の出力:

"server01
server02
server03",owner,/etc/file1
"server04",owner,/etc/file2
"server05
server06",owner,/etc/file3

これを達成するためにsedを使用する方法は?

答え1

,owner,フィールドにラップする残りのテキストがすべてある場合:

GNUの使用sed:

sed -E ':1; s/,(.*,owner,)/\r\n\1/; t1; s/(.*)(,owner,)/"\1"\2/' file

そしてperl

perl -pe 's{.*?(?=,owner,)}{q(") . ($& =~ s/,/\r\n/gr) . q(")}e' file

最後の2つのフィールドを除くすべての項目がある場合:

GNUの使用sed:

sed -E ':1; s/(.*),(.*,.*,)/\1\r\n\2/; t1; s/(.*),/"\1",/' file

そしてperl

perl -pe 's{(.*)(?=,.*,)}{q(") . ($& =~ s/,/\r\n/gr) . q(")}e' file

または、Text::CSV正しいCSVの解析とフォーマットのためにPerlモジュールを使用してください。

perl -MText::CSV -e '
  $csv = Text::CSV->new({binary => 1, decode_utf8 => 0, eol => $" = "\r\n"});
  while ($row = $csv->getline(STDIN)) {
    if (($last = $#{$row}) > 1) {
      $csv->print(STDOUT, ["@$row[0..$last-2]", @$row[$last-1..$last]]);
    } else {
      $csv->print(STDOUT, $row);
    }
  }' < file

ファイルがBOMでUTF-16またはUTF-8でエンコードされている場合(Microsoftファイルでは前例のない作業)、調整する必要があります(この方法については参考資料を参照)、何らかの方法でエンコードする必要があります。通常の入力を処理できるようにperldoc Text::CSVフォーマットを再指定する方法です。<file dos2unix | ... | unix2dos

答え2

私はこれをしませんsed。私は次のものを使用しますperl(またはおそらくawk- しかし、Perlの組み込み機能を使用する代わりに私自身のpop()関数を書く必要があります):join()

$ perl -F, -lane '$file = pop @F; $owner = pop @F;
                  print join(",", "\"" . join("\r\n", @F) . "\"", $owner, $file)' input.csv 
"server01
server02
server03",owner,/etc/file1
"server04",owner,/etc/file2
"server05
server06",owner,/etc/file3

まず、配列から最後の2つの要素(所有者とファイル名)を削除し@F(オプションがカンマをフィールド区切り文字として使用するように指定するため)、@F各入力行に対して自動的に生成されます - awkが入力を自動的に分割する方法に似ています)追加します。変数 sum に保存されます。 。-a-F,$file$owner

"\"" . join("\r\n", @F) . "\""@Fの各要素はCR + LF文字で区切られ、文字列全体が二重引用符で囲まれた文字列を構成します。

$owner文字列は、およびと連結して(カンマで)$file印刷されます。

答え3

各レコードの最後の2つのフィールドまで(含まれていない)、次のすべてのフィールドと結合された最初のカンマ区切りフィールドを引用して、引用符付きCSVフィールドを作成しようとしています。次に、結合フィールドに含まれるコンマをCR + LFに置き換える必要があります。

ファイルの各行の内容を置き換えて、2番目のカンマの後に二重引用符を挿入し、行を反転させ、先頭に二重引用符を挿入することで、簡単にこれを行うことができます。

$ rev file | sed 's/,/,"/2' | rev | sed 's/^/"/'
"server01,server02,server03",owner,/etc/file1
"server04",owner,/etc/file2
"server05,server06",owner,/etc/file3

正しく参照されているヘッダーのないCSVファイルがあるので、次のものを使用できます。ミラーmlr;ツール具体的に構造化データ(CSVなど)を処理するには、最初のフィールドのすべてのコンマをCR + LFに置き換えます。

$ rev file | sed 's/,/,"/2' | rev | sed 's/^/"/' | mlr --csv -N put '$1 = gsub($1, ",", "\r\f")'
server01
server02
server03,owner,/etc/file1
server04,owner,/etc/file2
server05
server06,owner,/etc/file3

Unixシステムでは、フィールドとレコード区切り文字はこのデータセットのフィールドに含まれていないため、フィールドを引用する必要はありません。別々の呼び出しで、各レコードから2番目のフィールドを抽出してそれを表示できますmlr

$ rev file | sed 's/,/,"/2' | rev | sed 's/^/"/' | mlr --csv -N put '$1 = gsub($1, ",", "\r\f")' | mlr --csv -N cut -f 2
owner
owner
owner

sed最後のコマンド出力の元の引用を維持するには、次のようにします--quote-original

$ rev file | sed 's/,/,"/2' | rev | sed 's/^/"/' | mlr --csv -N --quote-original put '$1 = gsub($1, ",", "\r\f")'
"server01
server02
server03",owner,/etc/file1
"server04",owner,/etc/file2
"server05
server06",owner,/etc/file3

ただし、理想的には、後処理ステップでフィールドを変更するのではなく、最初からフィールドが正しくなるように破損したCSV出力を生成するコードを変更する必要があります。


Millerのみを使用:

mlr --nidx --fs comma put '
    for (var i=2; NF > 3; i=i+1) {
        $1 .= "\r\f" . $[i];
        unset $[i]
    }
    $1 = "\"" . $1 . "\""' file

これは、ファイルを単純なカンマで区切られ、暗黙的に索引付けされたテキストファイルとして読み取ります。次に、CR + LFを区切り文字として使用して、2番目と2番目のフィールドを最初のフィールドの末尾に追加し、3つのフィールドのみが残るまで各追加フィールドを削除します。次に、最初のフィールドを明示的に参照します。

関連情報