
У меня есть этот входной файл в системе 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
Не используйте цикл оболочки для обработки текста. Используйте утилиту для обработки текста.
Здесь, чтобы писать имена с заглавной буквы в 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
Немного проще с GNU awk
и его 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
Если вам приходится использовать цикл оболочки, по крайней мере используйте оболочку с оператором заглавных букв:
#! /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
изменив \u
на \u\L
и в awk
, применив tolower()
ко второй части каждого слова.
Если бы вам пришлось использовать только bash
встроенные команды и их, как вы указали в комментариях, это было бы гораздо более обременительно (помимо того, что неэффективно), поскольку bash имеет очень ограниченное количество операторов по сравнению, например, с zsh или ksh93, а его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
будут заглавными как , Jean_pierre
а другие будут заглавными как Jean_Pierre
. Вам может потребоваться адаптироваться к вашему конкретному вводу (также рассмотрите возможность объединения символов, что также добавит гаечный ключ в работу здесь). Смотрите такжеLingua::EN::NameCase
perl
модуль для обработки еще большего количества особых случаев.
Что касается того, какие команды устанавливаются по умолчанию на каких системах. Большинство систем будут иметь perl
(возможно Text::CSV
, модуль, но, скорее всего, не Lingua::EN::NameCase
тот) и POSIX-совместимые awk
реализации sh
, многие (даже некоторые не-GNU системы) имеют bash
(оболочку GNU), несколько имеют GNU awk (хотя не некоторые основанные на GNU системы, такие как Ubuntu, которые по крайней мере в некоторых версиях предпочитают mawk). В настоящее время немногие установили zsh
по умолчанию.
CentOS, будучи системой GNU, должна иметь bash
и gawk
установленными по умолчанию в дополнение к perl
. bash
и gawk
даже предоставлять sh
и awk
там.
решение2
Если все ваши входные данные представляют собой простые двухсловные имена, состоящие только из английских букв, без заглавных букв в середине слова, как в опубликованном вами примере, то с помощью любого awk в любой оболочке на каждой машине 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,,,
решение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 -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
выбирает 5-й ( e
) столбец из каждого объекта и извлекает каждое слово в нем. Каждое слово имеет свой первый символ, преобразованный в верхний регистр с помощью функции ascii_upcase
, jq
а затем результат выводится как правильно заключенные в кавычки данные CSV.
Учитывая данные в вопросе, это приведет к
1,,,,"Ivan Petrov",,67,
2,2,,,"Vasia Pupkin","director",8,
3,,,,"John Lenon",,,
Это также справится с полями CSV, содержащими встроенные запятые и символы новой строки.