Если вы не хотите, чтобы какие-либо переменные раскрывались внутри документа здесь, укажите EOD:
cat << 'EOD'
...
EOD
Например этот файл:
cat <<EOF
test\$literal
var$(echo this)
EOF
дает такой результат:
test$literal
varthis
Не используйте цикл оболочки для обработки текста . Используйте утилиту для обработки текста.
Здесь, чтобы написать имена в 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
не могут читать разделенные значения .
Это должно быть что-то вроде (здесь, предполагая bash 4.0+ для оператора ${var^}
):
#! /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
для \w
— это alnums + underscore, вы также обнаружите разницу для таких строк, как 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
.
Использование 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, содержащими встроенные запятые и символы новой строки.
Альтернативный дубль:
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
Если весь ваш ввод состоит из 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,,,