J'essaye de lire un fichier csv dans un tableau associatif bash mais je n'obtiens pas les résultats que j'attends.

Utilisation de Bash 5.0.18

Bellum:fox3-api rocky$ bash --version
GNU bash, version 5.0.18(1)-release (x86_64-apple-darwin19.5.0)

Contenu de foobar.csv

Bellum:scripts rocky$ cat ./foobar.csv
foo-1,bar-1
foo-2,bar-2
foo-3,bar-3

Contenu de problem.sh

#!/usr/bin/env bash

declare -A descriptions
while IFS=, read name title; do
      echo "I got:$name|$title"
      descriptions[$name]=$title
done < foobar.csv

echo ${descriptions["foo-1"]}
echo ${descriptions["foo-2"]}
echo ${descriptions["foo-3"]}

Sortie réelle de problem.sh

Bellum:scripts rocky$ ./problem.sh
I got:foo-1|bar-1
I got:foo-2|bar-2

bar-2

Bellum:scripts rocky$

Sortie désirée:

I got:foo-1|bar-1
I got:foo-2|bar-2
I got:foo-3|bar-3    
bar-1
bar-2
bar-3

Commentaires sur les sorties demandées

    Bellum:scripts rocky$ head -n 1 ./foobar.csv | hexdump -C
    00000000  ef bb bf 66 6f 6f 2d 31  2c 62 61 72 2d 31 0d 0a  |...foo-1,bar-1..|
    00000010
    Bellum:scripts rocky$ od -c foobar.csv
    0000000  357 273 277   f   o   o   -   1   ,   b   a   r   -   1  \r  \n
    0000020    f   o   o   -   2   ,   b   a   r   -   2  \r  \n   f   o   o
    0000040    -   3   ,   b   a   r   -   3
    0000050

Changement dos2unix de Cyrus

    #!/usr/bin/env bash
    
    declare -A descriptions
    dos2unix < foobar.csv | while IFS=, read name title; do
          echo "I got:$name|$title"
          descriptions[$name]=$title
    done
    
    echo ${descriptions["foo-1"]}
    echo ${descriptions["foo-2"]}
    echo ${descriptions["foo-3"]}

Sortie du changement dos2unix de Cyrus

    Bellum:scripts rocky$ ./problem.sh
    I got:foo-1|bar-1
    I got:foo-2|bar-2
    
    
    
    
    Bellum:scripts rocky$

Le fichier csv est créé sur un Mac en enregistrant au format csv à partir de Microsoft Excel. Merci d'avance pour tout renseignement.

Solution hybride

Pour les futurs gens, ce problème était en fait deux problèmes. Le premier consistait à enregistrer mon fichier CSV à partir d'un classeur Microsoft Excel pour Mac. J'ai enregistré sous ... Format "CSV UTF-8" (le premier format de fichier CSV répertorié dans le menu déroulant d'Excel). Cela ajoute des octets supplémentaires qui ont gâché la commande de lecture dans bash. Fait intéressant, ces octets n'apparaîtront pas dans une commande cat (voir la description originale du problème de publication). L'enregistrement du CSV à partir d'Excel en tant que "Valeurs séparées par des virgules" (beaucoup plus bas dans la liste déroulante des formats) a éliminé ce premier problème.

Deuxièmement, @ Léa Gris et @glenn jackman m'ont indiqué la bonne direction pour les modificateurs de mon script qui ont aidé avec certains caractères de retour à la ligne et de retour chariot présents dans le fichier enregistré Excel.

Merci tout le monde. J'ai passé une journée entière à essayer de comprendre cela. Leçon apprise: j'aurais dû me tourner vers Stackoverflow beaucoup plus tôt.

3
dmjones 25 oct. 2020 à 00:30

3 réponses

Meilleure réponse

Voici pourquoi vous n'obtenez pas le résultat attendu:

    Bellum:scripts rocky$ od -c foobar.csv
    0000000  357 273 277   f   o   o   -   1   ,   b   a   r   -   1  \r  \n
    0000020    f   o   o   -   2   ,   b   a   r   -   2  \r  \n   f   o   o
    0000040    -   3   ,   b   a   r   -   3
    0000050
  1. le nom sur la première ligne ne contient pas seulement "foo-1" - il y a des caractères supplémentaires là-dedans.
    • Ils peuvent être supprimés avec "${name#$'\357\273\277'}"
  2. la dernière ligne ne se termine pas par une nouvelle ligne, donc la boucle while-read ne se répète que deux fois.
    • read renvoie une valeur différente de zéro s'il ne peut pas lire une ligne entière, même s'il lit certains caractères.
    • puisque read renvoie "false", la boucle while se termine.
    • cela peut être contourné en utilisant:
       while IFS=, read -r name title || [[ -n $title ]]; do ... 
      #............................. ^^^^^^^^^^^^^^^^^^ 
       
    • ou, corrigez simplement le fichier.

Résultat:

BOM=$'\357\273\277'
CR=$'\r'

declare -A descriptions
while IFS=, read name title || [[ $title ]]; do
  descriptions["${name#$BOM}"]=${title%$CR}
done < foobar.csv

declare -p descriptions
echo "${descriptions["foo-1"]}"
echo "${descriptions["foo-2"]}"
echo "${descriptions["foo-3"]}"
declare -A descriptions=([foo-1]="bar-1" [foo-2]="bar-2" [foo-3]="bar-3" )
bar-1
bar-2
bar-3
3
glenn jackman 25 oct. 2020 à 13:27

Cela fonctionnera avec votre fichier d'entrée, indépendamment des nouvelles lignes Unix ou DOS, indépendamment d'un marqueur de nomenclature UTF-8, et peu importe si la dernière ligne a un marqueur de nouvelle ligne ou non avant la fin du fichier:

#!/usr/bin/env bash

declare -A descriptions
# IFS=$',\r\n' allow to capture either Unix or DOS Newlines
# read -r warrant not to expand \ escaped special characters
# || [ "$name" ] will make sure to capture last line
# even if it does not end with a newline marker
while IFS=$',\r\n' read -r name title || [ "$name" ]; do
      echo "I got:$name|$title"
      descriptions[$name]=$title
done < <(
  # Filter-out UTF-8 BOM if any
  sed $'1s/^\357\353\277//' foobar.csv
)

echo "${descriptions["foo-1"]}"
echo "${descriptions["foo-2"]}"
echo "${descriptions["foo-3"]}"

# A shorter option for debug, is to dump the variable as a declaration
typeset -p descriptions

Maintenant, un moyen très compact de transférer votre CSV dans le tableau associatif en une seule fois

#!/usr/bin/env bash

# shellcheck disable=SC2155 # Safe generated assignment with printf %q
declare -A descriptions="($(
  # Collect all values from file into an array
  IFS=$'\r\n,' read -r -d '' -a elements < <(
    # Discard the UTF-8 BOM from the input file if any
    sed $'1s/^\357\353\277//' foobar.csv
  )
  # Format the elements into an Associative array declaration [key]=value 
  printf '[%q]=%q ' "${elements[@]}"
))"

echo "${descriptions["foo-1"]}"
echo "${descriptions["foo-2"]}"
echo "${descriptions["foo-3"]}"

# A shorter option for debug, is to dump the variable as a declaration
typeset -p descriptions
2
Léa Gris 25 oct. 2020 à 02:48

Le problème est avec les 3 premiers octets, vous pouvez les supprimer avec:

dd bs=1 skip=3 if=foobar.csv of=foobar2.csv

Et essayez avec foobar2.csv

1
Philippe 24 oct. 2020 à 23:25