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's\w是 alnums + 下划线,因此您还会发现字符串之间的区别,jean_pierre其中的perl字符串大写为 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 字段。

相关内容