
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
perl
El 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 awk
la 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 awk
y 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::NameCase
basado) se diferencia de los demás en que se convierte éric serRA
en Éric Serra
en lugar de, Éric SerRA
por ejemplo. Puede lograr el mismo resultado perl
cambiando \u
a \u\L
y awk
aplicando tolower()
la segunda parte de cada palabra.
Si solo tuviera que usar bash
y 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 -a
no 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 \w
es alnums + guión bajo, también encontrará una diferencia para cadenas como jean_pierre
las que perl
se escribirían en mayúscula Jean_pierre
mientras 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
perl
mó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::CSV
módulo, pero probablemente no el indicado) e implementaciones Lingua::EN::NameCase
compatibles 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.awk
sh
bash
zsh
CentOS, al ser un sistema GNU, debería tener bash
e gawk
instalarse de forma predeterminada además de perl
. bash
e gawk
incluso proporcionar sh
y awk
allí.
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 csvjson
desdecsvkitpara convertir su archivo CSV en JSON y luego modificarlo conjq
antes 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 csvjson
comando 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 jq
expresión selecciona la quinta e
columna ( ) de cada objeto y extrae cada palabra que contiene. Cada palabra tiene su primer carácter convertido a mayúscula usando la ascii_upcase
función de jq
y 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.