Dans un article précédent, nous avons parlé de la commande de coupe qui peut être utilisée pour extraire des colonnes d'un fichier de données texte CSV ou tabulaire.
Le paste
La commande fait exactement le contraire :elle fusionne plusieurs fichiers d'entrée pour produire un nouveau fichier texte délimité à partir d'eux. Nous allons voir comment utiliser efficacement la commande Coller sous Linux et Unix.
7 exemples pratiques de la commande Coller sous Linux
Si vous préférez les vidéos, vous pouvez regarder cette vidéo expliquant les mêmes exemples de commandes Coller abordés dans cet article.
1. Coller des colonnes
Dans son cas d'utilisation le plus basique, le paste
la commande prend N fichiers d'entrée et joignez-les ligne par ligne sur la sortie :
sh$ printf "%s\n" {a..e} | tee letters
a
b
c
d
e
sh$ printf "%s\n" {1..5} | tee digits
1
2
3
4
5
sh$ paste letters digits
a 1
b 2
c 3
d 4
e 5
Mais laissons maintenant les explications théoriques pour travailler sur un exemple pratique. Si vous avez téléchargé les exemples de fichiers utilisés dans la vidéo ci-dessus, vous pouvez voir que j'ai plusieurs fichiers de données correspondant aux différentes colonnes d'un tableau :
sh$ head -3 *.csv
==> ACCOUNTLIB.csv <==
ACCOUNTLIB
TIDE SCHEDULE
VAT BS/ENC
==> ACCOUNTNUM.csv <==
ACCOUNTNUM
623477
445452
==> CREDIT.csv <==
CREDIT
<--- empty line
<--- empty line
==> DEBIT.csv <==
DEBIT
00000001615,00
00000000323,00
Il est assez facile de produire un fichier texte délimité par des tabulations à partir de ces données :
sh$ paste *.csv | head -3
ACCOUNTLIB ACCOUNTNUM CREDIT DEBIT
TIDE SCHEDULE 623477 00000001615,00
VAT BS/ENC 445452 00000000323,00
Comme vous pouvez le voir, lorsqu'il est affiché sur la console, le contenu de ce fichier de valeurs séparées par des tabulations ne produit pas un tableau parfaitement formaté. Mais c'est par conception :le paste
La commande n'est pas utilisée pour créer des fichiers texte à largeur fixe, mais uniquement des fichiers texte délimités où un caractère donné se voit attribuer le rôle de séparateur de champs.
Donc, même si ce n'est pas évident dans la sortie ci-dessus, il y a en fait un et un seul caractère de tabulation entre chaque champ. Rendons cela évident en utilisant la commande sed :
sh$ paste *.csv | head -3 | sed -n l
ACCOUNTLIB\tACCOUNTNUM\tCREDIT\tDEBIT$
TIDE SCHEDULE\t623477\t\t00000001615,00$
VAT BS/ENC\t445452\t\t00000000323,00$
Désormais, les caractères invisibles sont affichés sans ambiguïté dans la sortie. Et vous pouvez voir les caractères de tabulation affichés sous la forme \t
. Vous pouvez les compter :il y a toujours trois onglets sur chaque ligne de sortie — un entre chaque champ. Et quand vous en voyez deux d'affilée, cela signifie seulement qu'il y avait un champ vide là-bas. C'est souvent le cas dans mes fichiers d'exemple particuliers puisque sur chaque ligne, le champ CREDIT ou DEBIT est défini, mais jamais les deux en même temps.
2. Modification du délimiteur de champ
Comme nous l'avons vu, le paste
La commande utilise le caractère de tabulation comme séparateur de champ par défaut ("délimiteur"). Quelque chose que nous pouvons changer en utilisant le -d
option. Supposons que je souhaite utiliser un point-virgule à la place :
# The quotes around the ';' are used to prevent the
# shell to consider that semi-colon as being a command separator
sh$ paste -d ';' *.csv | head -3
ACCOUNTLIB;ACCOUNTNUM;CREDIT;DEBIT
TIDE SCHEDULE;623477;;00000001615,00
VAT BS/ENC;445452;;00000000323,00
Pas besoin d'ajouter le sed
commande à la fin du pipeline ici puisque le séparateur que nous avons utilisé est un caractère imprimable. Quoi qu'il en soit, le résultat est le même :sur une ligne donnée, chaque champ est séparé de son voisin par un délimiteur à un caractère.
3. Transposer des données en utilisant le mode série
Les exemples ci-dessus ont une chose en commun :le paste
La commande lit tous ses fichiers d'entrée en parallèle, ce qui est nécessaire pour pouvoir les fusionner ligne par ligne dans la sortie.
Mais le paste
la commande peut également fonctionner en mode dit série , activé en utilisant le -s
drapeau. Comme son nom l'indique, en mode série, le paste
commande lira les fichiers d'entrée l'un après l'autre. Le contenu du premier fichier d'entrée sera utilisé pour produire la première ligne de sortie. Ensuite, le contenu du deuxième fichier d'entrée sera utilisé pour produire la deuxième ligne de sortie, et ainsi de suite. Cela signifie également que la sortie aura autant de lignes qu'il y avait de fichiers dans l'entrée.
Plus formellement, les données extraites du fichier N apparaîtra comme le N ème ligne dans la sortie en mode série, alors qu'elle apparaîtrait comme le N ème colonne en mode "parallèle" par défaut. Mathématiquement, la table obtenue en mode série est la transposée de la table produite en mode par défaut (et inversement ).
Pour illustrer cela, considérons un petit sous-échantillon de nos données :
sh$ head -5 ACCOUNTLIB.csv | tee ACCOUNTLIB.sample
ACCOUNTLIB
TIDE SCHEDULE
VAT BS/ENC
PAYABLES
ACCOMMODATION GUIDE
sh$ head -5 ACCOUNTNUM.csv | tee ACCOUNTNUM.sample
ACCOUNTNUM
623477
445452
4356
623372
En mode par défaut ("parallèle"), les données du fichier d'entrée serviront de colonnes dans la sortie, produisant un tableau de deux colonnes sur cinq lignes :
sh$ paste *.sample
ACCOUNTLIB ACCOUNTNUM
TIDE SCHEDULE 623477
VAT BS/ENC 445452
PAYABLES 4356
ACCOMMODATION GUIDE 623372
Mais en mode série, les données du fichier d'entrée apparaîtront sous forme de lignes, produisant maintenant un tableau de cinq colonnes sur deux lignes :
sh$ paste -s *.sample
ACCOUNTLIB TIDE SCHEDULE VAT BS/ENC PAYABLES ACCOMMODATION GUIDE
ACCOUNTNUM 623477 445452 4356 623372
4. Travailler avec l'entrée standard
Comme beaucoup d'utilitaires standards, le paste
La commande peut utiliser l'entrée standard pour lire les données. Soit implicitement lorsqu'il n'y a pas de nom de fichier donné en argument, soit explicitement en utilisant le spécial -
nom de fichier. Apparemment, ce n'est pas très utile :
# Here, the paste command is useless
head -5 ACCOUNTLIB.csv | paste
ACCOUNTLIB
TIDE SCHEDULE
VAT BS/ENC
PAYABLES
ACCOMMODATION GUIDE
Je vous encourage à le tester par vous-même, mais la syntaxe suivante devrait produire le même résultat, rendant à nouveau la commande coller inutile dans ce cas :
head -5 ACCOUNTLIB.csv | paste -
Alors, quel pourrait être l'intérêt de lire des données à partir de l'entrée standard ? Eh bien, avec le -s
drapeau, les choses deviennent beaucoup plus intéressantes comme nous allons le voir maintenant.
4.1. Joindre les lignes d'un fichier
Comme nous l'avons vu quelques paragraphes plus tôt, en mode série, la commande coller écrira toutes les lignes d'un fichier d'entrée sur la même ligne de sortie. Cela nous donne un moyen simple de joindre toutes les lignes lues à partir de l'entrée standard en une seule ligne de sortie (potentiellement très longue) :
sh$ head -5 ACCOUNTLIB.csv | paste -s -d':'
ACCOUNTLIB:TIDE SCHEDULE:VAT BS/ENC:PAYABLES:ACCOMMODATION GUIDE
C'est essentiellement la même chose que vous pourriez faire en utilisant le tr
commande, mais avec une différence cependant. Utilisons le diff
utilitaire pour repérer cela :
sh$ diff <(head -5 ACCOUNTLIB.csv | paste -s -d':') \
<(head -5 ACCOUNTLIB.csv | tr '\n' ':')
1c1
< ACCOUNTLIB:TIDE SCHEDULE:VAT BS/ENC:PAYABLES:ACCOMMODATION GUIDE
---
> ACCOUNTLIB:TIDE SCHEDULE:VAT BS/ENC:PAYABLES:ACCOMMODATION GUIDE:
\ No newline at end of file
Tel que rapporté par le diff
utilitaire, nous pouvons voir le tr
La commande a remplacé chaque instance du caractère de saut de ligne par le délimiteur donné, y compris le tout dernier. Par contre, le paste
La commande gardait le dernier caractère de saut de ligne intact. Donc, selon que vous avez besoin ou non du délimiteur après le tout dernier champ, vous utiliserez une commande ou l'autre.
4.2. Formatage multi-colonnes d'un fichier d'entrée
Selon les spécifications de l'Open Group, "l'entrée standard doit être lue une ligne à la fois" par le paste
commande. Ainsi, en passant plusieurs occurrences du -
nom de fichier spécial comme arguments du paste
la commande entraînera l'écriture d'autant de lignes consécutives de l'entrée dans la même ligne de sortie :
sh$ seq 9 | paste - - -
1 2 3
4 5 6
7 8 9
Pour rendre les choses plus claires, je vous encourage à étudier la différence entre les deux commandes ci-dessous. Dans le premier cas, la commande coller ouvre trois fois le même fichier, ce qui entraîne une duplication des données dans la sortie. Par contre, dans le second cas le fichier ACCOUNTLIB n'est ouvert qu'une seule fois (par le shell), mais lu trois fois pour chaque ligne (par le paste
commande), ce qui entraîne l'affichage du contenu du fichier sur trois colonnes :
sh$ paste ACCOUNTLIB.csv ACCOUNTLIB.csv ACCOUNTLIB.csv | head -2
ACCOUNTLIB ACCOUNTLIB ACCOUNTLIB
TIDE SCHEDULE TIDE SCHEDULE TIDE SCHEDULE
sh$ paste - - - < ACCOUNTLIB.csv | head -2
ACCOUNTLIB TIDE SCHEDULE VAT BS/ENC
PAYABLES ACCOMMODATION GUIDE VAT BS/ENC
Étant donné le comportement du paste
commande lors de la lecture à partir de l'entrée standard, ce n'est généralement pas conseillé d'utiliser plusieurs -
noms de fichiers spéciaux en mode série. Dans ce cas, la première occurrence lira l'entrée standard jusqu'à sa fin, et les occurrences suivantes de -
lirait à partir d'un flux d'entrée déjà épuisé, ce qui entraînerait l'absence de données supplémentaires :
# The following command will produce 3 lines of output.
# But the first one exhausted the standard input,
# so the remaining two lines are empty
sh$ seq 9 | paste -s - - -
1 2 3 4 5 6 7 8 9
5. Travailler avec des fichiers de longueur différente
Les spécifications Open Group pour le paste
utilitaire sont assez clairs :
Si une condition de fin de fichier est détectée sur un ou plusieurs fichiers d'entrée, mais pas sur tous les fichiers d'entrée, coller se comportera comme si des lignes vides étaient lues à partir des fichiers sur lesquels la fin de fichier a été détectée, à moins que la -s l'option est spécifiée.
Ainsi, le comportement est celui auquel vous pouvez vous attendre :les données manquantes sont remplacées par du contenu « vide ». Pour illustrer ce comportement, enregistrons quelques transactions supplémentaires dans notre "base de données". Afin de conserver intacts les fichiers d'origine, nous travaillerons cependant sur une copie de nos données :
# Copy files
sh$ for f in ACCOUNTNUM ACCOUNTLIB CREDIT DEBIT; do
cp ${f}.csv NEW${f}.csv
done
# Update the copy
sh$ cat - << EOF >> NEWACCOUNTNUM.csv
1080
4356
EOF
sh$ cat - << EOF >> NEWDEBIT.csv
00000001207,35
EOF
sh$ cat - << EOF >> NEWCREDIT.csv
00000001207,35
EOF
Avec ces mises à jour, nous avons maintenant enregistré un nouveau mouvement de capital du compte #1080 au compte #4356. Cependant, comme vous l'avez peut-être remarqué, je n'ai pas pris la peine de mettre à jour le fichier ACCOUNTLIB. Cela ne semble pas un si gros problème car le paste
remplacera les lignes manquantes par des données vides :
sh$ paste -d';' NEWACCOUNTNUM.csv \
NEWACCOUNTLIB.csv \
NEWDEBIT.csv \
NEWCREDIT.csv | tail
4356;PAYABLES;;00000000402,03
613866;RENTAL COSTS;00000000018,00;
4356;PAYABLES;;00000000018,00
657991;MISCELLANEOUS CHARGES;00000000015,00;
445333;VAT BS/DEBIT;00000000003,00;
4356;PAYABLES;;00000000018,00
626510;LANDLINE TELEPHONE;00000000069,14;
445452;VAT BS/ENC;00000000013,83;
1080;;00000001207,35; # <-- the account label is missing here
4356;;;00000001207,35 # <-- the account label is missing here
Mais attention, le paste
la commande ne peut faire correspondre les lignes que par leur physique position :tout ce qu'il peut dire, c'est qu'un fichier est "plus court" qu'un autre. Pas où les données manquent. Ainsi, il ajoute toujours les champs vides à la fin de la sortie, ce qui peut provoquer des décalages inattendus dans vos données. Rendons cela évident en ajoutant encore une autre transaction :
sh$ cat << EOF >> NEWACCOUNTNUM.csv
4356
3465
EOF
sh$ cat << EOF >> NEWACCOUNTLIB.csv
PAYABLES
WEB HOSTING
EOF
sh$ cat << EOF >> NEWDEBIT.csv
00000000706,48
EOF
sh$ cat << EOF >> NEWCREDIT.csv
00000000706,48
EOF
Cette fois, j'ai été plus rigoureux puisque j'ai correctement mis à jour à la fois le numéro de compte (ACCOUNTNUM), et son libellé correspondant (ACCOUNTLIB) ainsi que les fichiers de données CREDIT et DEBIT. Mais comme il manquait des données dans l'enregistrement précédent, le paste
la commande n'est plus capable de garder les champs liés sur la même ligne :
sh$ paste -d';' NEWACCOUNTNUM.csv \
NEWACCOUNTLIB.csv \
NEWDEBIT.csv \
NEWCREDIT.csv | tail
4356;PAYABLES;;00000000018,00
657991;MISCELLANEOUS CHARGES;00000000015,00;
445333;VAT BS/DEBIT;00000000003,00;
4356;PAYABLES;;00000000018,00
626510;LANDLINE TELEPHONE;00000000069,14;
445452;VAT BS/ENC;00000000013,83;
1080;PAYABLES;00000001207,35;
4356;WEB HOSTING;;00000001207,35
4356;;;00000000706,48
3465;;00000000706,48;
Comme vous pouvez le voir, le compte #4356 est signalé avec le libellé "HÉBERGEMENT WEB" alors qu'en réalité, ce dernier devrait apparaître sur la ligne correspondant au compte #3465.
En conclusion, si vous devez traiter des données manquantes, au lieu du paste
commande, vous devriez envisager d'utiliser le join
utilitaire car ce dernier correspondra aux lignes en fonction de leur contenu, et non en fonction de leur position dans le fichier d'entrée. Cela le rend beaucoup plus adapté aux applications de style "base de données". J'ai déjà publié une vidéo sur le join
commande, mais cela devrait probablement mériter un article à part entière, alors faites-nous savoir si ce sujet vous intéresse !
6. Cycle sur les délimiteurs
Dans la très grande majorité des cas d'utilisation, vous ne fournirez qu'un seul caractère comme délimiteur. C'est ce que nous avons fait jusqu'à présent. Cependant, si vous donnez plusieurs caractères après le -d
option, la commande coller les parcourra :le premier caractère sera utilisé comme premier délimiteur de champ sur la ligne, le deuxième caractère comme deuxième délimiteur de champ, et ainsi de suite.
sh$ paste -d':+-' ACCOUNT*.csv CREDIT.csv DEBIT.csv | head -5
ACCOUNTLIB:ACCOUNT NUM+CREDIT-DEBIT
TIDE SCHEDULE:623477+-00000001615,00
VAT BS/ENC:445452+-00000000323,00
PAYABLES:4356+00000001938,00-
ACCOMODATION GUIDE:623372+-00000001333,00
Les délimiteurs de champs ne peuvent apparaître qu'entre des champs. Pas au bout d'une ligne. Et vous ne pouvez pas insérer plus d'un délimiteur entre deux champs donnés. Comme astuce pour surmonter ces limitations, vous pouvez utiliser le /dev/null
fichier spécial comme entrée supplémentaire où vous avez besoin d'un séparateur supplémentaire :
# Display the opening bracket between the
# ACCOUNTLIB field and the ACCOUNTNUM field, and
# the closing bracket between the ACCOUNTNUM field
# and the empty `/dev/null` field:
sh$ paste -d'()' \
ACCOUNT*.csv /dev/null | head -5
ACCOUNTLIB(ACCOUNTNUM)
TIDE SCHEDULE(623477)
VAT BS/ENC(445452)
PAYABLES(4356)
ACCOMODATION GUIDE(623372)
Quelque chose dont vous pourriez même abuser :
sh$ paste -d'# is ' \
- ACCOUNTNUM.csv - - - ACCOUNTLIB.csv < /dev/null | tail -5
#657991 is MISCELLANEOUS CHARGES
#445333 is VAT BS/DEBIT
#4356 is PAYABLES
#626510 is LANDLINE TELEPHONE
#445452 is VAT BS/ENC
Cependant, inutile de dire que si vous atteignez ce niveau de complexité, cela pourrait être un indice du paste
l'utilitaire n'était pas nécessairement le meilleur outil pour le travail. Peut-être vaut-il la peine d'envisager, dans ce cas, quelque chose d'autre comme sed
ou la commande awk.
Mais que se passe-t-il si la liste contient moins de délimiteurs que nécessaire pour afficher une ligne dans la sortie ? Fait intéressant, le paste
commande les "cyclera" dessus. Ainsi, une fois la liste épuisée, le paste
La commande reviendra au premier délimiteur, ce qui ouvre probablement la porte à une utilisation créative. En ce qui me concerne, je n'ai rien pu faire de vraiment utile avec cette fonctionnalité compte tenu de mes données. Il faudra donc vous contenter de l'exemple un peu tiré par les cheveux suivant. Mais ce ne sera pas une perte de temps totale car c'était une bonne occasion de mentionner que vous devez doubler la barre oblique inverse (\\
) lorsque vous souhaitez l'utiliser comme délimiteur :
sh$ paste -d'/\\' \
- ACCOUNT*.csv CREDIT.csv DEBIT.csv - < /dev/null | tail -5
/MISCELLANEOUS CHARGES\657991/\00000000015,00/
/VAT BS/DEBIT\445333/\00000000003,00/
/PAYABLES\4356/00000000018,00\/
/LANDLINE TELEPHONE\626510/\00000000069,14/
/VAT BS/ENC\445452/\00000000013,83/
7. Délimiteurs de caractères multioctets
Comme la plupart des utilitaires Unix standard, la commande paste est née à un moment où un caractère équivalait à un octet. Mais ce n'est plus le cas :aujourd'hui, de nombreux systèmes utilisent par défaut l'encodage à longueur variable UTF-8. En UTF-8, un caractère peut être représenté par 1, 2, 3 ou 4 octets. Cela nous permet de mélanger dans le même fichier texte toute la variété de l'écriture humaine, ainsi que des tonnes de symboles et d'emojis, tout en maintenant une compatibilité ascendante avec l'ancien codage de caractères US-ASCII à un octet.
Disons par exemple que je voudrais utiliser le DIAMANT BLANC (◇ U+25C7) comme séparateur de champ. En UTF-8, ce caractère est codé à l'aide des trois octets e2 97 87
. Ce caractère peut être difficile à obtenir à partir du clavier, donc si vous voulez essayer par vous-même, je vous suggère de le copier-coller à partir du bloc de code ci-dessous :
# The sed part is only used as a little trick to add the
# row number as the first field in the output
sh$ sed -n = ACCOUNTNUM.csv |
paste -d'◇' - ACCOUNT*.csv | tail -5
26�MISCELLANEOUS CHARGES�657991
27�VAT BS/DEBIT�445333
28�PAYABLES�4356
29�LANDLINE TELEPHONE�626510
30�VAT BS/ENC�445452
Assez trompeur, n'est-ce pas? Au lieu du diamant blanc attendu, j'ai ce symbole "point d'interrogation" (du moins, c'est ainsi qu'il est affiché sur mon système). Ce n'est cependant pas un personnage "aléatoire". C'est le caractère de remplacement Unicode utilisé "pour indiquer des problèmes lorsqu'un système est incapable de restituer un flux de données à un symbole correct" . Alors, qu'est-ce qui ne va pas ?
Encore une fois, l'examen du contenu binaire brut de la sortie nous donnera quelques indices :
sh$ sed -n = ACCOUNTNUM.csv | paste -d'◇' - ACCOUNT*.csv | tail -5 | hexdump -C
00000000 32 36 e2 4d 49 53 43 45 4c 4c 41 4e 45 4f 55 53 |26.MISCELLANEOUS|
00000010 20 43 48 41 52 47 45 53 97 36 35 37 39 39 31 0a | CHARGES.657991.|
00000020 32 37 e2 56 41 54 20 42 53 2f 44 45 42 49 54 97 |27.VAT BS/DEBIT.|
00000030 34 34 35 33 33 33 0a 32 38 e2 50 41 59 41 42 4c |445333.28.PAYABL|
00000040 45 53 97 34 33 35 36 0a 32 39 e2 4c 41 4e 44 4c |ES.4356.29.LANDL|
00000050 49 4e 45 20 54 45 4c 45 50 48 4f 4e 45 97 36 32 |INE TELEPHONE.62|
00000060 36 35 31 30 0a 33 30 e2 56 41 54 20 42 53 2f 45 |6510.30.VAT BS/E|
00000070 4e 43 97 34 34 35 34 35 32 0a |NC.445452.|
0000007a
Nous avons déjà eu l'occasion de pratiquer avec les vidages hexadécimaux ci-dessus, donc vos yeux devraient maintenant être suffisamment aiguisés pour repérer les délimiteurs de champ dans le flux d'octets. En regardant bien, vous verrez que le séparateur de champs après le numéro de ligne est l'octet e2
. Mais si vous poursuivez vos recherches, vous remarquerez que le deuxième séparateur de champs est 97
. Non seulement le paste
La commande n'a pas sorti le caractère que je voulais, mais elle n'a pas non plus utilisé partout le même octet que le séparateur ?!?
Attendez une minute :cela ne vous rappelle-t-il pas quelque chose dont nous avons déjà parlé ? Et ces deux octets e2 97
, ne vous sont-ils pas familiers ? Eh bien, familier est probablement un peu trop, mais si vous sautez quelques paragraphes en arrière, vous pourriez les trouver mentionnés quelque part…
Alors tu as trouvé où c'était ? Auparavant, je l'ai dit en UTF-8, le losange blanc est codé comme les trois octets e2 97 87
. Et en effet, le paste
la commande a considéré cette séquence non pas comme un caractère entier de trois octets, mais comme trois caractères indépendants octets et donc, il a utilisé le premier octet comme premier séparateur de champ, puis le deuxième octet comme deuxième séparateur de champ.
Je vous laisse relancer cette expérience en ajoutant une colonne supplémentaire dans les données d'entrée ; vous devriez voir le troisième séparateur de champ être 87
— le troisième octet de la représentation UTF-8 pour le losange blanc.
Ok, c'est l'explication :le paste
La commande n'accepte que des "caractères" d'un octet comme séparateur. Et c'est particulièrement ennuyeux, car, encore une fois, je ne connais aucun moyen de surmonter cette limitation, sauf en utilisant le /dev/null
astuce que je t'ai déjà donnée :
sh$ sed -n = ACCOUNTNUM.csv |
paste -d'◇' \
- /dev/null /dev/null \
ACCOUNTLIB.csv /dev/null /dev/null \
ACCOUNTNUM.csv | tail -5
26◇MISCELLANEOUS CHARGES◇657991
27◇VAT BS/DEBIT◇445333
28◇PAYABLES◇4356
29◇LANDLINE TELEPHONE◇626510
30◇VAT BS/ENC◇445452
Si vous avez lu mon article précédent sur la cut
commande, vous vous souviendrez peut-être que j'ai eu des problèmes similaires avec l'implémentation GNU de cet outil. Mais j'ai remarqué à ce moment-là que l'implémentation d'OpenBSD prenait correctement en compte le LC_CTYPE
paramètres régionaux pour identifier les caractères multi-octets. Par curiosité, j'ai testé le paste
commande sur OpenBSD aussi. Hélas, avec le même résultat que sur ma box Debian cette fois, malgré les spécifications pour le paste
utilitaire mentionnant la variable d'environnement LC_CTYPE comme déterminant " les paramètres régionaux pour l'interprétation des séquences d'octets de données textuelles en tant que caractères (par exemple, caractères à un octet par opposition aux caractères à plusieurs octets dans les arguments et les fichiers d'entrée)" . D'après mon expérience, toutes les implémentations majeures du paste
l'utilitaire ignore actuellement les caractères multi-octets dans la liste des délimiteurs et suppose des séparateurs d'un octet. Mais je ne prétendrai pas avoir testé cela pour toute la variété des plateformes *nix. Donc si j'ai raté quelque chose ici, n'hésitez pas à utiliser la section des commentaires pour me corriger !
Astuce bonus :Éviter le piège \0
Pour des raisons historiques :
Les commandes :
coller -d "\0" … coller -d "" …
ne sont pas nécessairement équivalents ; ce dernier n'est pas spécifié par ce volume de IEEE Std 1003.1-2001 et peut entraîner une erreur. La construction '\0' est utilisée pour signifier "pas de séparateur" car les versions historiques de paste ne suivaient pas les directives de syntaxe, et la commande :
coller -d”” …
n'a pas pu être géré correctement par getopt().
Ainsi, le moyen portable de coller des fichiers sans utiliser de délimiteur consiste à spécifier le \0
délimiteur. Ceci est quelque peu contre-intuitif car, pour de nombreuses commandes, \0
signifie le caractère NUL - un caractère codé comme un octet composé uniquement de zéros qui ne doit entrer en conflit avec aucun contenu textuel.
Vous pouvez trouver le caractère NUL comme séparateur utile, en particulier lorsque vos données peuvent contenir des caractères arbitraires (comme lorsque vous travaillez avec des noms de fichiers ou des données fournies par l'utilisateur). Malheureusement, je ne connais aucun moyen d'utiliser le caractère NUL comme délimiteur de champ avec le paste
commande. Mais peut-être savez-vous comment faire ? Si tel est le cas, je serais plus qu'heureux de lire votre solution dans la section de commande.
Par contre, le paste
la partie implémentation de GNU Coreutils a le non-standard -z
option pour passer de la nouvelle ligne au caractère NUL pour le séparateur de ligne. Mais dans ce cas, le caractère NUL sera utilisé comme séparateur de ligne les deux pour l'entrée et la sortie. Donc, pour tester cette fonctionnalité, nous avons d'abord besoin d'une version terminée par zéro de nos fichiers d'entrée :
sh$ tr '\n' '\0' < ACCOUNTLIB.csv > ACCOUNTLIB.zero
sh$ tr '\n' '\0' < ACCOUNTNUM.csv > ACCOUNTNUM.zero
Pour voir ce qui a changé dans le processus, nous pouvons utiliser le hexdump
utilitaire pour examiner le contenu binaire brut des fichiers :
sh$ hexdump -C ACCOUNTLIB.csv | head -5
00000000 41 43 43 4f 55 4e 54 4c 49 42 0a 54 49 44 45 20 |ACCOUNTLIB.TIDE |
00000010 53 43 48 45 44 55 4c 45 0a 56 41 54 20 42 53 2f |SCHEDULE.VAT BS/|
00000020 45 4e 43 0a 50 41 59 41 42 4c 45 53 0a 41 43 43 |ENC.PAYABLES.ACC|
00000030 4f 4d 4f 44 41 54 49 4f 4e 20 47 55 49 44 45 0a |OMODATION GUIDE.|
00000040 56 41 54 20 42 53 2f 45 4e 43 0a 50 41 59 41 42 |VAT BS/ENC.PAYAB|
sh$ hexdump -C ACCOUNTLIB.zero | head -5
00000000 41 43 43 4f 55 4e 54 4c 49 42 00 54 49 44 45 20 |ACCOUNTLIB.TIDE |
00000010 53 43 48 45 44 55 4c 45 00 56 41 54 20 42 53 2f |SCHEDULE.VAT BS/|
00000020 45 4e 43 00 50 41 59 41 42 4c 45 53 00 41 43 43 |ENC.PAYABLES.ACC|
00000030 4f 4d 4f 44 41 54 49 4f 4e 20 47 55 49 44 45 00 |OMODATION GUIDE.|
00000040 56 41 54 20 42 53 2f 45 4e 43 00 50 41 59 41 42 |VAT BS/ENC.PAYAB|
Je vous laisse comparer par vous-même les deux dumps hexadécimaux ci-dessus pour identifier la différence entre les fichiers « .zero » et les fichiers texte originaux. Comme indice, je peux vous dire qu'une nouvelle ligne est encodée sous la forme 0a
octet.
J'espère que vous avez pris le temps nécessaire pour localiser le caractère NUL dans les fichiers d'entrée ".zero". Quoi qu'il en soit, nous avons maintenant une version terminée par zéro des fichiers d'entrée, nous pouvons donc utiliser le -z
option du paste
commande pour gérer ces données, produisant également dans la sortie un résultat terminé par zéro :
# Hint: in the hexadecimal dump:
# the byte 00 is the NUL character
# the byte 09 is the TAB character
# Look at any ASCII table to find the mapping
# for the letters or other symbols
# (https://en.wikipedia.org/wiki/ASCII#Character_set)
sh$ paste -z *.zero | hexdump -C | head -5
00000000 41 43 43 4f 55 4e 54 4c 49 42 09 41 43 43 4f 55 |ACCOUNTLIB.ACCOU|
00000010 4e 54 4e 55 4d 00 54 49 44 45 20 53 43 48 45 44 |NTNUM.TIDE SCHED|
00000020 55 4c 45 09 36 32 33 34 37 37 00 56 41 54 20 42 |ULE.623477.VAT B|
00000030 53 2f 45 4e 43 09 34 34 35 34 35 32 00 50 41 59 |S/ENC.445452.PAY|
00000040 41 42 4c 45 53 09 34 33 35 36 00 41 43 43 4f 4d |ABLES.4356.ACCOM|
# Using the `tr` utility, we can map \0 to newline
# in order to display the output on the console:
sh$ paste -z *.zero | tr '\0' '\n' | head -3
ACCOUNTLIB ACCOUNTNUM
TIDE SCHEDULE 623477
VAT BS/ENC 445452
Étant donné que mes fichiers d'entrée ne contiennent pas de nouvelles lignes intégrées dans les données, le -z
option est d'une utilité limitée ici. Mais sur la base des explications ci-dessus, je vous laisse essayer de comprendre pourquoi l'exemple suivant fonctionne "comme prévu". Pour bien comprendre que vous devez probablement télécharger les exemples de fichiers et les examiner au niveau de l'octet à l'aide du hexdump
utilitaire comme nous l'avons fait ci-dessus :
# Somehow, the head utility seems to be confused
# by the ACCOUNTS file content (I wonder why?;)
sh$ head -3 CATEGORIES ACCOUNTS
==> CATEGORIES <==
PRIVATE
ACCOMMODATION GUIDE
SHARED
==> ACCOUNTS <==
6233726230846265106159126579914356613866618193623477623795445333445452605751
# The output is quite satisfactory, putting the account number
# after the account name and keeping things surprisingly nicely formatted:
sh$ paste -z -d':' CATEGORIES ACCOUNTS | tr '\0' '\n' | head -5
PRIVATE
ACCOMMODATION GUIDE:623372
SHARED
ADVERTISEMENTS:623084
Quoi de plus ?
Le paste
La commande produit uniquement une sortie de texte délimité. Mais comme illustré à la fin de la vidéo d'introduction, si votre système prend en charge la column
BSD utilitaire, vous pouvez l'utiliser pour obtenir des tableaux bien formatés en convertissant le paste
la sortie de la commande dans un format de texte à largeur fixe. Mais cela fera l'objet d'un prochain article. Alors restez à l'écoute et, comme toujours, n'oubliez pas de partager cet article sur vos sites Web et réseaux sociaux préférés !