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

不要使用 shell 循環來處理文本。使用文字處理實用程式。

這裡,要將第 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

如果不是,作為近似值,您可以將一個或多個字母數字字元的每個序列的第一個字元轉換為大寫:

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

然而,這將無法正確處理諸如McGregor, van Dike... 或帶有組合字元的名稱。

(perl 還具有適當的 CSV 解析模組,以防您的輸入不僅僅是簡單的 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

如果必須使用 shell 循環,至少使用帶有大寫運算子的 shell:

#! /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

請注意,其中一個(以及Lingua::EN::NameCase基於它的)與其他的不同之處在於,它變成了éric serRA實例Éric Serra而不是Éric SerRA實例。perl透過將\uto\u\L和 inawk應用於tolower()每個單字的第二部分,您可以獲得相同的結果。

如果您必須僅使用bash及其內建命令(如您在註釋中指出的那樣),那將更加麻煩(除了效率低之外),因為與zsh 或ksh93 相比,bash 的操作符非常有限,而且它的操作符也非常有限。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 區域設定中,上面的內容é以UTF-8(0xc3 0xa9 位元組)編碼,而不是iso8859-1 或其他字元集) 。 bash(可能還有 awk)會因 NUL 位元組而阻塞。

由於perl'\w是 alnums + 下劃線,因此您還會發現字串之間的差異,其中jean_pierreperl字串大寫為 as,Jean_pierre而其他字串則大寫為Jean_Pierre。您可能需要適應您的特定輸入(也可以考慮組合字符,這也會在此處的工作中添加扳手)。另請參閱Lingua::EN::NameCase perl模組來處理更多特殊情況。

至於預設安裝在什麼系統上的命令。大多數系統都會有perl(可能是Text::CSV模組,但可能不是那個Lingua::EN::NameCase)和 POSIX 相容awksh實現,許多(甚至一些非 GNU 系統)有bash(GNU shell),有些有 GNU awk(儘管不是一些基於 GNU 的系統)例如Ubuntu ,至少在某些版本中喜歡mawk)。目前很少有zsh預設安裝的。

CentOS 作為 GNU 系統bashgawk除了perl.bash甚至gawk提供shawk那裡。

答案2

如果您的所有輸入都是所有英文字母的簡單 2 個單字名稱,沒有中間單字大寫,就像您發布的範例中那樣,那麼在每個 Unix 機器上的任何 shell 中使用任何 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

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

答案4

使用csvjson來自csvkit將 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 行都有一個對象。此表達式從每個物件中jq挑選出第五 ( ) 列並提取其中的每個單字。使用的函數e將每個單字的第一個字元轉換為大寫,然後將結果輸出為正確引用的 CSV 資料。ascii_upcasejq

鑑於問題中的數據,這將導致

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

這也可以處理包含嵌入逗號和換行符的 CSV 欄位。

相關內容