Bash : concaténer des fichiers *.csv

Tags: #<Tag:0x00007f509ff8d058>

Bonjour à tous,

Je me retrouve avec le cas suivant : j’ai deux dossiers (disons /home/user/A et /home/user/B). Chacun de ces dossiers contient des fichiers CSV et certains de ces fichiers sont similaires. Par exemple je peux avoir /home/user/A/xxx.csv et /home/user/B/xxx.csv.
J’ai également créé un 3è dossier /home/user/C.

Ce que j’aimerais faire, c’est un petit script qui me repère les fichiers csv qui sont identiques dans le dossier A et B et qui me les concatène dans le dossier C.

Avec l’exemple ci-dessus cela donnerait donc /home/user/C/xxx.csv = /home/user/A/xxx.csv + /home/user/B/xxx.csv.

D’avance merci :slight_smile:

Bonjour snake57

#!/bin/bash

for f in /home/user/A/*.csv; do
    if [ -e "/home/user/B/${f##*/}" ]; then
        cat "$f" "/home/user/B/${f##*/}" > "/home/user/C/${f##*/}"
    fi
done

J’n’ai pas essayé ; merci :slight_smile: bonne journée

Bonjour,

Qu’entends-tu par “identiques” ? Est-ce le nom ou le contenu ?
S’il s’agit du nom, je pense que le script de PicP est correct si et seulement si le nom de tes fichiers ne contient pas d’espace. S’il y a des espaces, je te propose de modifier la ligne du for comme cela

for f in /home/user/A/*.csv; do

en

for f in "/home/user/A/*.csv"; do

Pour ce qui est trouver les fichiers identiques dans le contenu, je te propose d’installer fpudes.

LeDub de retour sur Debian-fr

Bonjour à tous,

Je n’avais pas d’espace dans mes noms de fichier, le script de MicP à donc fonctionné parfaitement.

Toutefois je vais aller le remodifier en prenant ta remarque en compte LeDub. Comme cela si je m’en ressert il fonctionnera aussi avec des noms de fichiers qui contiennent des espaces.

Merci à tous

J’avais testé mon script avec des noms de fichiers dont un comportait des espaces:

Pour créer les répertoires et les fichiers qui vont servir au test,
copiez/collez la suite de lignes de commande suivantes
dans une fenêtre de terminal :

mkdir /home/$USER/{A,B,C}
touch /home/$USER/A/xxx_{1,2}.csv
echo 'contenu du fichier /home/$USER/A_xxx_3.csv' > /home/$USER/A/xxx_3.csv
echo 'contenu du fichier /home/$USER/B_xxx_3.csv' > /home/$USER/B/xxx_3.csv
echo 'contenu du fichier /home/$USER/A_xxx_4 avec espace.csv' > "/home/$USER/A/xxx_4 avec espace.csv"
echo 'contenu du fichier /home/$USER/B_xxx_4 avec espace.csv' > "/home/$USER/B/xxx_4 avec espace.csv"
echo 'contenu du fichier /home/$USER/B_xxx_5.csv' > /home/$USER/B/xxx_5.csv

Pour supprimer tous les répertoires et fichiers qui auraient été créés pour et par ce test :

rm -rf /home/$USER/{A,B,C}

Le test complet :

michel@debg53sw:~$ mkdir /home/$USER/{A,B,C}
michel@debg53sw:~$ touch /home/$USER/A/xxx_{1,2}.csv
michel@debg53sw:~$ echo 'contenu du fichier /home/$USER/A_xxx_3.csv' > /home/$USER/A/xxx_3.csv
michel@debg53sw:~$ echo 'contenu du fichier /home/$USER/B_xxx_3.csv' > /home/$USER/B/xxx_3.csv
michel@debg53sw:~$ echo 'contenu du fichier /home/$USER/A_xxx_4 avec espace.csv' > "/home/$USER/A/xxx_4 avec espace.csv"
michel@debg53sw:~$ echo 'contenu du fichier /home/$USER/B_xxx_4 avec espace.csv' > "/home/$USER/B/xxx_4 avec espace.csv"
michel@debg53sw:~$ echo 'contenu du fichier /home/$USER/B_xxx_5.csv' > /home/$USER/B/xxx_5.csv
michel@debg53sw:~$ ls -l /home/$USER/{A,B,C}
/home/$USER/A:
total 8
-rw-r--r-- 1 michel michel  0 avril 12 11:59 xxx_1.csv
-rw-r--r-- 1 michel michel  0 avril 12 11:59 xxx_2.csv
-rw-r--r-- 1 michel michel 44 avril 12 12:00 xxx_3.csv
-rw-r--r-- 1 michel michel 56 avril 12 12:00 xxx_4 avec espace.csv

/home/$USER/B:
total 12
-rw-r--r-- 1 michel michel 44 avril 12 12:00 xxx_3.csv
-rw-r--r-- 1 michel michel 56 avril 12 12:00 xxx_4 avec espace.csv
-rw-r--r-- 1 michel michel 44 avril 12 12:00 xxx_5.csv

/home/$USER/C:
total 0
michel@debg53sw:~$ for f in /home/$USER/A/*.csv; do if [ -e "/home/$USER/B/${f##*/}" ]; then cat "$f" "/home/$USER/B/${f##*/}" > "/home/$USER/C/${f##*/}"; fi; done
michel@debg53sw:~$ ls -l /home/$USER/{A,B,C}
/home/$USER/A:
total 8
-rw-r--r-- 1 michel michel  0 avril 12 11:59 xxx_1.csv
-rw-r--r-- 1 michel michel  0 avril 12 11:59 xxx_2.csv
-rw-r--r-- 1 michel michel 44 avril 12 12:00 xxx_3.csv
-rw-r--r-- 1 michel michel 56 avril 12 12:00 xxx_4 avec espace.csv

/home/$USER/B:
total 12
-rw-r--r-- 1 michel michel 44 avril 12 12:00 xxx_3.csv
-rw-r--r-- 1 michel michel 56 avril 12 12:00 xxx_4 avec espace.csv
-rw-r--r-- 1 michel michel 44 avril 12 12:00 xxx_5.csv

/home/$USER/C:
total 8
-rw-r--r-- 1 michel michel  88 avril 12 12:01 xxx_3.csv
-rw-r--r-- 1 michel michel 112 avril 12 12:01 xxx_4 avec espace.csv
michel@debg53sw:~$ cat /home/$USER/C/xxx_3.csv
contenu du fichier /home/$USER/A_xxx_3.csv
contenu du fichier /home/$USER/B_xxx_3.csv
michel@debg53sw:~$ cat "/home/$USER/C/xxx_4 avec espace.csv"
contenu du fichier /home/$USER/A_xxx_4 avec espace.csv
contenu du fichier /home/$USER/B_xxx_4 avec espace.csv
michel@debg53sw:~$ 
michel@debg53sw:~$ 
michel@debg53sw:~$ rm -rf /home/$USER/{A,B,C}   # Pour supprimer tous les répertoires et fichiers créés pour ce test
michel@debg53sw:~$ 

Bien sûr, dans toutes mes lignes de commandes,
j’aurais pu remplacer /home/$USER/
par ~/
ou bien par $HOME/
voire même par rien du tout puisque je lançais toutes ces lignes de commandes depuis le répertoire personnel de mon compte utilisateur.

Mais c’était juste pour que ça ressemble à la demande :

… /home/user/C/xxx.csv = /home/user/A/xxx.csv + /home/user/B/xxx.csv. …

et que j’avais la flemme de créer sur ma machine un compte utilisateur nommé user


@LeDub Tu devrais procéder à des test avant de proposer une solution

Non, le script que j’ai proposé a bien été testé et fonctionne avec des noms de fichiers comportant des caractères espaces
(et c’est MicP)

Ça ne fonctionnera pas, avec ou sans espaces dans les noms de fichiers.

michel@debtxt00:~$ touch fich{_Test_{1,2}," Test espaces "{3,4}}.csv
michel@debtxt00:~$ ls -l *.csv
-rw-r--r-- 1 michel michel 0 avril 12 20:38 fich_Test_1.csv
-rw-r--r-- 1 michel michel 0 avril 12 20:38 fich_Test_2.csv
-rw-r--r-- 1 michel michel 0 avril 12 20:38 fich Test espaces 3.csv
-rw-r--r-- 1 michel michel 0 avril 12 20:38 fich Test espaces 4.csv
michel@debtxt00:~$ compteur=1; for f in "./*.csv"; do echo "$compteur $f"; ((compteur++)); done
1 ./*.csv
michel@debtxt00:~$ compteur=1; for f in ./*.csv; do echo "$compteur $f"; ((compteur++)); done
1 ./fich_Test_1.csv
2 ./fich_Test_2.csv
3 ./fich Test espaces 3.csv
4 ./fich Test espaces 4.csv
michel@debtxt00:~$ 
michel@debtxt00:~$ rm fich{_Test_{1,2}," Test espaces "{3,4}}.csv  # Supprimer les fichiers créés pour ce test

@Arisoy Tu devrais procéder à des test avant de cocher un message comme étant la solution dans un fil de discussion.

Bonjour Monsieur MicP,

Je tiens à vous remercier de ce petit cours de shell. Je ne peux que m’incliner devant autant d’expertise.
Pour information, j’avais testé la sélection des fichier mais avec un “ls” en lieu et place du test de présence du fichier. Veuillez m’excuser de ma paresse.

Voici un exemple de mes tests
Le «for» sans protection, le «ls» avec protection :

$ for f in Debian-fr*; do ls -1 "$f"; done
Debian-fr toujours accueillant.txt
Debian-fr.txt

Ça fonctionne.

Le «for» avec protection, le «ls» sans protection :

$ for f in "Debian-fr*"; do ls -1 $f; done
Debian-fr toujours accueillant.txt
Debian-fr.txt

Ça fonctionne aussi.

Sans protection :

$ for f in Debian-fr*; do ls -1 $f; done
ls: impossible d'accéder à 'Debian-fr': Aucun fichier ou dossier de ce type
ls: impossible d'accéder à 'toujours': Aucun fichier ou dossier de ce type
ls: impossible d'accéder à 'accueillant.txt': Aucun fichier ou dossier de ce type
Debian-fr.txt

Ça plante.

Avec les 2 protection :

$ for f in "Debian-fr*"; do ls -1 "$f"; done
ls: impossible d'accéder à 'Debian-fr*': Aucun fichier ou dossier de ce type

Ça plante.

Conclusion : la protection est indispensable soit dans le «for» soit après le «do» mais sera en échec si elle est présente aux deux endroits.

Je confirme que le code proposé est correct. Encore une fois je suis désolé d’avoir douté de votre compétence.

En échange, je me ferai un immense plaisir de vous enseigner quelques rudiments de politesse.
Leçon N°1. L’usage du «bonjour» est grandement apprécié quand on s’adresse à une personne par message privé.

Ledub toujours aussi content d’apprendre que d’enseigner.

Bonjour à tous,

Autant pour moi je décoche la deuxième solution.

Même remarques concernant le message que tu m’as envoyé en privé. Rien n’empêche de rester courtois.

Dans tous les cas merci encore pour la solution proposée qui m’a bien aidé :wink:

Bonjour snake57
Bonjour LeDub

Désolé pour le bonjour manquant dans le message privé

EDIT : D’un autre côté, dire plusieurs fois bonjour à la même personne dans la même heure…

Mais je ne cherche pas d’excuses, le message était de fait trop court,
et finalement beaucoup trop “sec”.

Pour information,
voici le contenu intégral du message privé que j’avais envoyé à LeDub :
“Tu devrais procéder à des test avant de proposer une solution.”

Et le contenu intégral de celui que j’ai envoyé à Snake57 :
“Tu devrais procéder à des test avant de cocher un message comme étant la solution dans un fil de discussion.”

Vraiment désolé si j’ai pu sembler hautain ou malpoli,
j’aurai plutôt dû m’abstenir que de faire les choses à moitié.


Je ne suis pas du tout un expert, et très loin de l’être,
je suis juste un autodidacte (jamais eu de cour de programmation informatique).
Et ce n’est pas du tout de la fausse modestie,
il me faudrait être bien plus compétent que ça pour pouvoir l’être.


Je vous remercie pour ces messages bienveillants et modérés,
d’autres auraient été beaucoup plus agressifs pour moins que ça.

Je ne suis pas doué pour choisir les émoticônes,
sinon, j’en aurai trouvé un pour montrer que je suis très très sincèrement désolé.


Je crois que j’ai fini par trouver comment expliquer
pourquoi il ne faut pas mettre de guillemets entourant la liste (ou la variable qui sera développée en liste)

C’est parce que si on l’entoure de guillemets, la boucle for ne verra qu’un seul élément : la chaîne de caractère.

Voici une liste de lettres,
mais comme elle est enfermée dans une chaîne de caractères (entre guillemets)
la boucle for ne verra qu’un seul élément : la chaîne de caractère contenant la suite de lettres.

michel@debg53sw:~$ compteur=1; for lettre in "a b c d e f"; do echo "Élément N°$compteur : $lettre"; ((compteur++ ));done
Élément N°1 : a b c d e f
michel@debg53sw:~$ 

Par contre, sans les guillemets,
la boucle for verra bien la suite des 6 éléments.

michel@debg53sw:~$ compteur=1; for lettre in a b c d e f; do echo "Élément N°$compteur :  $lettre"; ((compteur++ ));done
Élément N°1 : a
Élément N°2 : b
Élément N°3 : c
Élément N°4 : d
Élément N°5 : e
6 f
michel@debg53sw:~$ 

Une autre démo avec la liste des fichiers csv
avec des printf pour montrer qu’il n’y a bien qu’un seul élément dans la liste
si elle est donnée entre guillemets à la boucle for

michel@debg53sw:~$ for f in "*.csv"; do printf '%s' '"'; printf '%s' "$f"; printf '%s\n' '"';done
"fich_Test_1.csvfich_Test_2.csvfich Test espaces 3.csvfich Test espaces 4.csvxxx_1.csvxxx_2.csvxxx_5.csvxxx_6.csv"
michel@debg53sw:~$ 
michel@debg53sw:~$ for f in *.csv; do printf '%s' '"'; printf '%s' "$f"; printf '%s\n' '"';done
"fich_Test_1.csv"
"fich_Test_2.csv"
"fich Test espaces 3.csv"
"fich Test espaces 4.csv"
"xxx_1.csv"
"xxx_2.csv"
"xxx_5.csv"
"xxx_6.csv"
michel@debg53sw:~$ 

Bonjour MicP (sans faute d’orthographe) bonjour le monde,

On classe l’affaire pour la forme et suis content de le faire.

Pour ce qui est du fond, j’ai toujours un doute mais pas sur ta compétence MicP !

En effet, le caractère “*” sera substitué par le nom des fichiers du répertoire dans lequel vous vous trouvez (attention aux tâches cron (ou CRON :sunglasses:). Par exemple, et sans vérifier :wink:, un “ls” donne le même résultat qu’un “echo *”.
L’espace, souvent considéré comme le caractère séparateur de champs, pose d’énormes problèmes qui peuvent être résolus par l’utilisation de " ou en modifiant la variable IFS.
En général, avant une boucle avec des fichiers, je sauvegarde la variable IFS puis la change avec un IFS=$(echo -en “\n\b”) (-e pour lui dire qu’on lui passe des commandes “\” , \n pour le changement de ligne et \b pour supprimer le caractère de fin de ligne)

LeDub quand même très proche de MicP :kissing: