Bash :title -case a csv поле

Если вы не хотите, чтобы какие-либо переменные раскрывались внутри документа здесь, укажите EOD:

cat << 'EOD'
...
EOD

Например этот файл:

cat <<EOF
test\$literal
var$(echo this)
EOF

дает такой результат:

test$literal
varthis
0
22.06.2021, 15:11
4 ответа

Не используйте цикл оболочки для обработки текста . Используйте утилиту для обработки текста.

Здесь, чтобы написать имена в 5 ом поле, если доступен модульLingua::EN::NameCaseperl:

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::NameCaseperlдля обработки еще более особых случаев.

Что касается того, какие команды установлены по умолчанию в каких системах. Большинство систем будут иметь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.

4
28.07.2021, 11:23

Использование 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_upcasejq, а затем результат выводится в виде правильно цитируемых данных CSV.

Учитывая данные в вопросе, это приведет к

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

Это также справится с полями CSV, содержащими встроенные запятые и символы новой строки.

0
28.07.2021, 11:23

Альтернативный дубль:

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
0
28.07.2021, 11:23

Если весь ваш ввод состоит из 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,,,
2
28.07.2021, 11:23

Теги

Похожие вопросы