Bash: CSV フィールドをタイトルケースにする

Bash: CSV フィールドをタイトルケースにする

CentOS システムに次の入力ファイルがあります:

1,,,,ivan petrov,,67,
2,2,,,Vasia pupkin,director,8,
3,,,,john Lenon,,,

タスクはそれを次のように変更することです:

1,,,,Ivan Petrov,,67,
2,2,,,Vasia Pupkin,director,8,
3,,,,John Lenon,,,

名前と姓は大文字で始まる必要があります

#!/bin/bash
while IFS="," read line
do
    ns=$(echo $line | awk -F, '{print $5}')
    name=$(echo $ns | awk '{print $1}')
    surname=$(echo $ns | awk '{print $2}')
    ns=$(echo ${name^} ${surname^})
    awk -v nm="$ns" 'BEGIN{FS=OFS=","}{$5=nm}1' accnew.csv
done < <(tail -n +2 accnew.csv) > 1new.csv

これは私のスクリプトですが、正しく動作しません。

答え1

テキスト処理にシェルループを使用しないテキスト処理ユーティリティを使用します。

ここで、5番目のフィールドの名前を大文字にするには、Lingua::EN::NameCase perlモジュールが利用可能です:

perl -Mopen=locale -MLingua::EN::NameCase -F, -ae '
  $F[4] = nc $F[4] unless @F < 5;
  print join ",", @F' < your-file

そうでない場合は、近似値として、1 つ以上の英数字のシーケンスの最初の文字を大文字に変換できます。

perl -Mopen=locale -F, -ae '
  $F[4] =~ s/\w+/\u$&/g unless @F < 5;
  print join ",", @F' < your-file

ただし、、McGregor...などの名前van Dikeや結合文字を含む名前は適切に処理されません。

(入力がサンプル内の引用符なしの単純な csv だけではない場合は、perl に適切な CSV 解析モジュールもあります)。

同じことは標準構文でも実行できますawkが、はるかに面倒です。

awk -F, -v OFS=, '
  NF >= 5 {
    r = $5; $5 = ""
    while (match(r, "[[:alnum:]]+")) {
      $5 = $5 substr(r, 1, RSTART - 1) \
           toupper(substr(r, RSTART, 1)) \
           substr(r, RSTART + 1, RLENGTH - 1)
      r = substr(r, RSTART + RLENGTH)
    }
    $5 = $5 r
  }
  {print}' < your-file

GNUawkとそのpatsplit()機能を使用すると少し簡単になります:

gawk -F, -v OFS=, '
  NF >= 5 {
    n = patsplit($5, f, /[[:alnum:]]+/, s)
    $5 = s[0]
    for (i = 1; i <= n; i++)
      $5 = $5 toupper(substr(f[i], 1, 1)) \
              substr(f[i], 2) s[i]
  }
  {print}' < your-file

シェル ループを使用する必要がある場合は、少なくとも大文字演算子を含むシェルを使用してください。

#! /bin/zsh -
while IFS=, read -ru3 -A fields; do
  (( $#fields < 5 )) || fields[5]=${(C)fields[5]}
  print -r -- ${(j[,])fields} || exit
done 3< your-file

1 つ (および基になるもの) は、たとえば の代わりに になるLingua::EN::NameCaseという点で他のものと異なることに注意してください。を に変更し、 を各単語の 2 番目の部分に適用することで、同じ結果を得ることができます。éric serRAÉric SerraÉric SerRAperl\u\u\Lawktolower()

コメントで指摘されているように、組み込みコマンドのみを使用しなければならない場合、bashbashはzshやksh93などに比べて演算子が非常に限られているため、はるかに面倒になります(非効率的です)。read -a分離された値を読み取ることができません

それは次のようになります (ここでは${var^}演算子として bash 4.0+ を想定しています)。

#! /bin/bash -
set -o noglob -o nounset
IFS=,
re='^([^[:alnum:]]*)([[:alnum:]]+)(.*)$'
while IFS= read -ru3 line; do
  fields=( $line'' )
  if (( ${#fields[@]} >= 5 )); then
    rest="${fields[4]}" fields[4]=
    while [[ "$rest" =~ $re ]]; do
      fields[4]="${fields[4]}${BASH_REMATCH[1]}${BASH_REMATCH[2]^}"
      rest="${BASH_REMATCH[3]}"
    done
  fi
  printf '%s\n' "${fields[*]}" || exit
done 3< your-file

これらは、入力がユーザーのロケール文字セットでエンコードされた有効なテキストであることを前提としています (たとえば、UTF-8 ロケールでは、上記はéiso8859-1 またはその他の文字セットではなく、UTF-8 (0xc3 0xa9 バイト) でエンコードされています)。bash (およびおそらく awk) のものは、NUL バイトで詰まってしまいます。

perl'sは alnums + アンダースコアなので、 のような\w文字列でも違いが見られます。 はを大文字にしますが、他の文字列では を大文字にします。特定の入力に合わせて調整する必要があるかもしれません(文字を組み合わせることも検討してください。これもここで問題を引き起こします)。jean_pierreperlJean_pierreJean_PierreLingua::EN::NameCase perlさらに特殊なケースを処理するためのモジュール。

どのコマンドがどのシステムにデフォルトでインストールされるかという点については、ほとんどのシステムにはperl(Text::CSVモジュールは存在するかもしれませんが、おそらく 1 つではないでしょうLingua::EN::NameCase) と POSIX 準拠の実装awkがありsh、多くのシステム (一部の非 GNU システムも) にはbash(GNU シェル) があり、いくつかのシステムには GNU awk があります (ただし、Ubuntu などの一部の GNU ベースのシステムには存在しません。Ubuntu は少なくとも一部のバージョンでは mawk を優先します)。現在、zshデフォルトでインストールされているシステムはほとんどありません。

CentOS は GNU システムなので、 に加えてbash、 がgawkデフォルトでインストールされているはずですperlbashさらに、とgawkも提供されています。shawk

答え2

投稿された例のように、入力がすべて、単語の途中に大文字がない、すべて英語の文字の単純な 2 単語の名前である場合、すべての Unix ボックスの任意のシェルで任意の awk を使用すると、次のようになります。

$ awk '
    BEGIN { FS=OFS="," }
    { split($5,ns," "); $5 = uc(ns[1]) " " uc(ns[2]) }
    { print }
    function uc(str) { return toupper(substr(str,1,1)) substr(str,2) }
' file
1,,,,Ivan Petrov,,67,
2,2,,,Vasia Pupkin,director,8,
3,,,,John Lenon,,,

答え3

別の bash の見方:

while IFS=, read -ra fields; do
  read -ra name <<<"${fields[4]}"
  fields[4]=${name[*]^}
  (IFS=,; echo "${fields[*]}")
done < file
1,,,,Ivan Petrov,,67
2,2,,,Vasia Pupkin,director,8
3,,,,John Lenon,,

とパール

perl -F, -lane '
    $F[4] = join " ", map {ucfirst} split " ", $F[4];
    print join ",", @F;
' file

答え4

csvjsonからの使用csvキットCSVファイルをJSONに変換し、jq変更したデータをCSVとして出力する前に:

csvjson -H file |
jq -r '
    .[].e |= gsub(
        "(?<a>[[:alnum:]]+)"; 
        .a | sub("(?<b>.)"; .b | ascii_upcase)) |
    .[] | map(.) | @csv'

このcsvjsonコマンドは、CSV ファイルを JSON ドキュメントに変換します。このドキュメントでは、元の CSV 行ごとに 1 つのオブジェクトを持つ配列の各列にアルファベットのキーが付けられます。式は各オブジェクトからjq5 番目の列 ( ) を選択し、そこに含まれる各単語を抽出します。各単語の最初の文字は の関数eを使用して大文字に変換され、結果は適切に引用符で囲まれた CSV データとして出力されます。ascii_upcasejq

質問のデータを考慮すると、次のようになります

1,,,,"Ivan Petrov",,67,
2,2,,,"Vasia Pupkin","director",8,
3,,,,"John Lenon",,,

これにより、埋め込まれたコンマや改行を含む CSV フィールドにも対応できます。

関連情報