Bash: Groß-/Kleinschreibung eines CSV-Felds

Bash: Groß-/Kleinschreibung eines CSV-Felds

Ich habe diese Eingabedatei auf einem CentOS-System:

1,,,,ivan petrov,,67,
2,2,,,Vasia pupkin,director,8,
3,,,,john Lenon,,,

Die Aufgabe besteht darin, es wie folgt zu ändern:

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

Vor- und Nachname müssen mit einem Großbuchstaben beginnen

#!/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

Das ist mein Skript, aber es funktioniert nicht richtig.

Antwort1

Verwenden Sie keine Shell-Schleife zur Textverarbeitung. Verwenden Sie ein Textverarbeitungsprogramm.

Hier, um Namen im 5. Feld groß zu schreiben , wenn dieLingua::EN::NameCase perlModul ist verfügbar:

perl -Mopen=locale -MLingua::EN::NameCase -F, -ae '
  $F[4] = nc $F[4] unless @F < 5;
  print join ",", @F' < your-file

Wenn nicht, können Sie als Annäherung das erste Zeichen jeder Folge von einem oder mehreren alphanumerischen Zeichen in einen Großbuchstaben umwandeln:

perl -Mopen=locale -F, -ae '
  $F[4] =~ s/\w+/\u$&/g unless @F < 5;
  print join ",", @F' < your-file

Dadurch werden jedoch Namen wie McGregor, van Dike... oder solche mit kombinierenden Zeichen nicht richtig verarbeitet.

(Perl verfügt auch über entsprechende CSV-Analysemodule für den Fall, dass Ihre Eingabe nicht nur aus der einfachen CSV-Datei ohne Anführungszeichen in Ihrem Beispiel besteht).

Dasselbe lässt sich mit der Standardsyntax erreichen awk, ist aber wesentlich umständlicher:

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

Etwas einfacher mit GNU awkund seiner patsplit()Funktion:

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

Wenn Sie eine Shell-Schleife verwenden müssen, verwenden Sie zumindest eine Shell mit einem Großschreibungsoperator:

#! /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

Beachten Sie, dass sich dieses (und das Lingua::EN::NameCasebasierende) von den anderen dadurch unterscheidet, dass es sich éric serRAin „in Éric Serra“ statt in Éric SerRA„for example“ verwandelt. Sie können dasselbe Ergebnis in erzielen, indem Sie „to“ und „in“ perländern , indem Sie dies auf den zweiten Teil jedes Wortes anwenden .\u\u\Lawktolower()

Wenn Sie nur die integrierten Befehle und verwenden müssten, bashwie Sie in den Kommentaren angeben, wäre das viel umständlicher (und ineffizienter), da Bash im Vergleich zu denen von zsh oder ksh93 beispielsweise sehr begrenzte Operatoren hat und seineread -akann getrennte Werte nicht lesen.

Das müsste ungefähr so ​​aussehen (hier wird Bash 4.0+ als ${var^}Operator vorausgesetzt):

#! /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

Dabei wird davon ausgegangen, dass die Eingabe ein gültiger Text ist, der im lokalen Zeichensatz des Benutzers codiert ist (beispielsweise éist das obige in einem UTF-8-Gebietsschema in UTF-8 (0xc3 0xa9 Bytes) codiert, nicht in iso8859-1 oder einem anderen Zeichensatz). Die Bash- (und möglicherweise Awk-) Versionen versagen bei NUL-Bytes.

Da perl's \waus Zahlen + Unterstrich besteht, werden Sie auch einen Unterschied bei Zeichenfolgen wie feststellen, jean_pierredie perlals großgeschrieben werden, Jean_pierrewährend die anderen es als großschreiben Jean_Pierre. Möglicherweise müssen Sie es an Ihre spezifische Eingabe anpassen (erwägen Sie auch die Kombination von Zeichen, was hier ebenfalls einen Strich durch die Rechnung machen würde). Siehe auch dieLingua::EN::NameCase perlModul zur Behandlung noch speziellerer Fälle.

Was die standardmäßig auf welchen Systemen installierten Befehle betrifft. Die meisten Systeme verfügen über perl(möglicherweise das Text::CSVModul, aber wahrscheinlich nicht das Lingua::EN::NameCaseeine) und eine POSIX-kompatible awkImplementierung sh, viele (sogar einige Nicht-GNU-Systeme) haben bash(die GNU-Shell), einige haben GNU awk (allerdings nicht einige GNU-basierte Systeme wie Ubuntu, die zumindest in einigen Versionen mawk bevorzugen). Nur wenige haben derzeit zshstandardmäßig installiert.

Da es sich bei CentOS um ein GNU-System handelt, sollte es zusätzlich zu standardmäßig bashund installiert sein und dort sogar und bereitstellen .gawkperlbashgawkshawk

Antwort2

Wenn Ihre gesamten Eingaben aus einfachen Zweiwortnamen mit ausschließlich englischen Buchstaben ohne Großbuchstaben in der Wortmitte bestehen, wie in Ihrem geposteten Beispiel, dann verwenden Sie ein beliebiges awk in einer beliebigen Shell auf jeder Unix-Box:

$ 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,,,

Antwort3

Eine alternative Bash-Version:

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,,

und Perl

perl -F, -lane '
    $F[4] = join " ", map {ucfirst} split " ", $F[4];
    print join ",", @F;
' file

Antwort4

Verwendung csvjsonvoncsvkitum Ihre CSV-Datei in JSON umzuwandeln und sie dann mitjqbevor die geänderten Daten als CSV ausgegeben werden:

csvjson -H file |
jq -r '
    .[].e |= gsub(
        "(?<a>[[:alnum:]]+)"; 
        .a | sub("(?<b>.)"; .b | ascii_upcase)) |
    .[] | map(.) | @csv'

Der csvjsonBefehl konvertiert Ihre CSV-Datei in ein JSON-Dokument mit alphabetischen Schlüsseln für jede Spalte in einem Array mit einem Objekt pro ursprünglicher CSV-Zeile. Der jqAusdruck wählt die 5. ( ) Spalte aus jedem Objekt aus und extrahiert jedes Wort darin. Der erste Buchstabe jedes Wortes wird mithilfe der Funktion von ein Großbuchstaben umgewandelt , und das Ergebnis wird dann als korrekt zitierte CSV-Daten ausgegeben.ascii_upcasejq

Angesichts der Daten in der Frage würde dies dazu führen,

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

Dies würde auch mit CSV-Feldern zurechtkommen, die eingebettete Kommas und Zeilenumbrüche enthalten.

verwandte Informationen