
Eu tenho este arquivo de entrada em um sistema CentOS:
1,,,,ivan petrov,,67,
2,2,,,Vasia pupkin,director,8,
3,,,,john Lenon,,,
A tarefa é alterá-lo para:
1,,,,Ivan Petrov,,67,
2,2,,,Vasia Pupkin,director,8,
3,,,,John Lenon,,,
Nome e Sobrenome devem começar com letra maiú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
Esse é o meu script, mas não funciona corretamente.
Responder1
Não use um shell loop para processar texto. Use um utilitário de processamento de texto.
Aqui, para colocar os nomes em maiúscula no 5º campo , se oLingua::EN::NameCase
perl
módulo está disponível:
perl -Mopen=locale -MLingua::EN::NameCase -F, -ae '
$F[4] = nc $F[4] unless @F < 5;
print join ",", @F' < your-file
Caso contrário, como aproximação, você poderia converter para maiúscula o primeiro caractere de cada sequência de um ou mais caracteres alfanuméricos:
perl -Mopen=locale -F, -ae '
$F[4] =~ s/\w+/\u$&/g unless @F < 5;
print join ",", @F' < your-file
No entanto, isso não lidaria adequadamente com nomes como McGregor
, van Dike
... ou aqueles com caracteres combinados.
(perl também possui módulos de análise CSV adequados, caso sua entrada não seja apenas o csv simples sem citar sua amostra).
O mesmo pode ser feito com awk
a sintaxe padrão, mas é muito mais complicado:
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
Um pouco mais fácil com o GNU awk
e sua patsplit()
função:
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
Se você tiver que usar um shell loop, pelo menos use um shell com um operador de capitalização:
#! /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
Observe que aquele (e o Lingua::EN::NameCase
baseado) difere dos outros porque se transforma éric serRA
em, Éric Serra
em vez de, Éric SerRA
por exemplo. Você pode obter o mesmo resultado perl
alterando \u
para \u\L
e awk
aplicando tolower()
à segunda parte de cada palavra.
Se você tivesse que usar apenas bash
seus comandos internos conforme indicado nos comentários, isso seria muito mais complicado (além de ser ineficiente), pois o bash tem operadores muito limitados em comparação com os do zsh ou ksh93, por exemplo, e seuread -a
não consigo ler valores separados.
Isso teria que ser algo como (aqui assumindo o bash 4.0+ para o ${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
Eles assumem que a entrada é um texto válido codificado no conjunto de caracteres de localidade do usuário (por exemplo, em uma localidade UTF-8, o é
acima é codificado em UTF-8 (0xc3 0xa9 bytes), não em iso8859-1 ou outro conjunto de caracteres). Os bash (e possivelmente awk) irão engasgar com bytes NUL.
Como perl
é \w
alnums + sublinhado, você também encontrará uma diferença para strings como jean_pierre
which perl
seria capitalizada como Jean_pierre
enquanto as outras seriam capitalizadas como Jean_Pierre
. Você pode precisar se adaptar à sua entrada específica (considere também combinar caracteres que também seriam um obstáculo aqui). Veja também oLingua::EN::NameCase
perl
módulo para lidar com casos ainda mais especiais.
Quanto a quais comandos são instalados por padrão em quais sistemas. A maioria dos sistemas terá perl
(possivelmente o Text::CSV
módulo, mas provavelmente não Lingua::EN::NameCase
aquele) e um compatível com POSIX awk
e sh
implementações, muitos (até mesmo alguns sistemas não-GNU) têm bash
(o shell GNU), vários têm GNU awk (embora não alguns sistemas baseados em GNU como o Ubuntu que pelo menos em algumas versões prefere o mawk). Poucos atualmente têm zsh
instalado por padrão.
O CentOS, sendo um sistema GNU, deve ter bash
e gawk
ser instalado por padrão, além do perl
. bash
e gawk
até mesmo fornecer sh
e awk
aí.
Responder2
Se todas as suas entradas forem nomes simples de 2 palavras, todas as letras em inglês, sem letras maiúsculas no meio, como no exemplo postado, use qualquer awk em qualquer shell em cada caixa 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,,,
Responder3
Um bash alternativo:
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,,
e perl
perl -F, -lane '
$F[4] = join " ", map {ucfirst} split " ", $F[4];
print join ",", @F;
' file
Responder4
Usando csvjson
decsvkitpara transformar seu arquivo CSV em JSON e, em seguida, modificá-lo comjq
antes de gerar os dados modificados como CSV:
csvjson -H file |
jq -r '
.[].e |= gsub(
"(?<a>[[:alnum:]]+)";
.a | sub("(?<b>.)"; .b | ascii_upcase)) |
.[] | map(.) | @csv'
O csvjson
comando converte seu arquivo CSV em um documento JSON com chaves alfabéticas para cada coluna em uma matriz com um objeto por linha CSV original. A jq
expressão seleciona a 5ª e
coluna ( ) de cada objeto e extrai cada palavra dela. Cada palavra tem seu primeiro caractere convertido em maiúsculas usando a ascii_upcase
função de jq
e o resultado é então gerado como dados CSV devidamente citados.
Dados os dados da pergunta, isso resultaria em
1,,,,"Ivan Petrov",,67,
2,2,,,"Vasia Pupkin","director",8,
3,,,,"John Lenon",,,
Isso também funcionaria com campos CSV contendo vírgulas e novas linhas incorporadas.