Bash: título en mayúsculas y minúsculas en un campo csv

Bash: título en mayúsculas y minúsculas en un campo csv

Tengo este archivo de entrada en un sistema CentOS:

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

La tarea es cambiarlo a:

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

Nombre y Apellido deben comenzar con letra mayúscula

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

Ese es mi script, pero no funciona correctamente.

Respuesta1

No utilice un bucle de shell para procesar texto. Utilice una utilidad de procesamiento de texto.

Aquí, para poner en mayúscula los nombres en el quinto campo , si elLingua::EN::NameCase perlEl módulo está disponible:

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

Si no, como aproximación, podrías convertir a mayúscula el primer carácter de cada secuencia de uno o más alfanuméricos:

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

Sin embargo, eso no manejaría adecuadamente nombres como McGregor, van Dike... o aquellos con caracteres combinados.

(Perl también tiene módulos de análisis CSV adecuados en caso de que su entrada no sea solo el csv simple sin citar en su muestra).

Se puede hacer lo mismo con awkla sintaxis estándar, pero es mucho más engorroso:

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

Un poco más fácil con GNU awky su patsplit()función:

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

Si tiene que usar un bucle de shell, al menos use un shell con un operador de mayúsculas:

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

Tenga en cuenta que ese (y el Lingua::EN::NameCasebasado) se diferencia de los demás en que se convierte éric serRAen Éric Serraen lugar de, Éric SerRApor ejemplo. Puede lograr el mismo resultado perlcambiando \ua \u\Ly awkaplicando tolower()la segunda parte de cada palabra.

Si solo tuviera que usar bashy sus comandos integrados como indica en los comentarios, sería mucho más engorroso (además de ineficiente) ya que bash tiene operadores muy limitados en comparación con los de zsh o ksh93, por ejemplo, y suread -ano puedo leer valores separados.

Eso tendría que ser algo como (aquí asumiendo bash 4.0+ para el ${var^}operador):

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

Estos suponen que la entrada es texto válido codificado en el conjunto de caracteres locales del usuario (por ejemplo, en una configuración regional UTF-8, lo éanterior está codificado en UTF-8 (0xc3 0xa9 bytes), no en iso8859-1 u otro conjunto de caracteres). Los bash (y posiblemente awk) se ahogarán con bytes NUL.

Como perl's \wes alnums + guión bajo, también encontrará una diferencia para cadenas como jean_pierrelas que perlse escribirían en mayúscula Jean_pierremientras que las otras se escribirían en mayúscula como Jean_Pierre. Es posible que deba adaptarse a su entrada específica (también considere combinar caracteres, lo que también supondría un problema aquí). Ver también elLingua::EN::NameCase perlmódulo para manejar casos aún más especiales.

En cuanto a qué comandos están instalados de forma predeterminada en qué sistemas. La mayoría de los sistemas tendrán perl(posiblemente el Text::CSVmódulo, pero probablemente no el indicado) e implementaciones Lingua::EN::NameCasecompatibles con POSIX , muchos (incluso algunos sistemas que no son GNU) tienen (el shell GNU), varios tienen GNU awk (aunque no algunos sistemas basados ​​en GNU). como Ubuntu que al menos en algunas versiones prefiere mawk). Actualmente son pocos los que los tienen instalados por defecto.awkshbashzsh

CentOS, al ser un sistema GNU, debería tener bashe gawkinstalarse de forma predeterminada además de perl. bashe gawkincluso proporcionar shy awkallí.

Respuesta2

Si toda su entrada son nombres simples de 2 palabras de todas las letras en inglés sin mayúsculas a mitad de palabra, como en el ejemplo publicado, entonces use cualquier awk en cualquier shell en cada cuadro de Unix:

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

Respuesta3

Una toma alternativa de 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,,

y perla

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

Respuesta4

Usando csvjsondesdecsvkitpara convertir su archivo CSV en JSON y luego modificarlo conjqantes de generar los datos modificados como CSV:

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

El csvjsoncomando convierte su archivo CSV en un documento JSON con claves alfabéticas para cada columna en una matriz con un objeto por línea CSV original. La jqexpresión selecciona la quinta ecolumna ( ) de cada objeto y extrae cada palabra que contiene. Cada palabra tiene su primer carácter convertido a mayúscula usando la ascii_upcasefunción de jqy luego el resultado se genera como datos CSV entrecomillados correctamente.

Dados los datos de la pregunta, esto daría como resultado

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

Esto también funcionaría con campos CSV que contienen comas y nuevas líneas incrustadas.

información relacionada