Dans un script Bash, je voudrais diviser une ligne en morceaux et les stocker dans un tableau.
La ligne:
Paris, France, Europe
Je voudrais les avoir dans un tableau comme celui-ci:
array[0] = Paris
array[1] = France
array[2] = Europe
Je voudrais utiliser du code simple, la vitesse de la commande n'a pas d'importance. Comment puis-je le faire?
,
(espace virgule) et non sur un seul caractère tel que la virgule. Si vous n'êtes intéressé que par ce dernier, les réponses sont plus faciles à suivre: stackoverflow.com/questions/918886/…cut
c'est une commande bash utile à garder à l'esprit également. Le séparateur est définissable en.wikibooks.org/wiki/Cut Vous pouvez également extraire des données d'une structure d'enregistrement à largeur fixe. en.wikipedia.org/wiki/Cut_(Unix) computerhope.com/unix/ucut.htmRéponses:
On notera que les caractères
$IFS
sont traités individuellement en tant que séparateurs de sorte que dans ce cas , les champs peuvent être séparés par soit une virgule ou un espace plutôt que la séquence des deux personnages. Fait intéressant cependant, les champs vides ne sont pas créés lorsque un espace virgule apparaît dans l'entrée car l'espace est traité spécialement.Pour accéder à un élément individuel:
Pour parcourir les éléments:
Pour obtenir à la fois l'index et la valeur:
Le dernier exemple est utile car les tableaux Bash sont rares. En d'autres termes, vous pouvez supprimer un élément ou ajouter un élément, puis les indices ne sont pas contigus.
Pour obtenir le nombre d'éléments dans un tableau:
Comme mentionné ci-dessus, les tableaux peuvent être rares, vous ne devez donc pas utiliser la longueur pour obtenir le dernier élément. Voici comment vous pouvez dans Bash 4.2 et versions ultérieures:
dans n'importe quelle version de Bash (quelque part après 2.05b):
Des décalages négatifs plus grands sélectionnent plus loin à la fin du tableau. Notez l'espace avant le signe moins dans l'ancien formulaire. C'est requis.
la source
IFS=', '
, vous n'aurez pas à supprimer les espaces séparément. Test:IFS=', ' read -a array <<< "Paris, France, Europe"; echo "${array[@]}"
declare -p array
pour la sortie de test.France, Europe, "Congo, The Democratic Republic of the"
cela se divisera après le Congo.str="Paris, France, Europe, Los Angeles"; IFS=', ' read -r -a array <<< "$str"
se divisera enarray=([0]="Paris" [1]="France" [2]="Europe" [3]="Los" [4]="Angeles")
note. Donc, cela ne fonctionne qu'avec des champs sans espaces car ilIFS=', '
s'agit d'un ensemble de caractères individuels - pas d'un délimiteur de chaîne.Toutes les réponses à cette question sont fausses d'une manière ou d'une autre.
Mauvaise réponse # 1
1: Il s'agit d'une mauvaise utilisation de
$IFS
. La valeur de la$IFS
variable n'est pas considérée comme un seul séparateur de chaînes de longueur variable , mais plutôt comme un ensemble de séparateurs de chaînes à un caractère , où chaque champ quiread
se sépare de la ligne d'entrée peut être terminé par n'importe quel caractère de l'ensemble (virgule ou espace, dans cet exemple).En fait, pour les vrais tenanciers, la pleine signification de
$IFS
est un peu plus impliquée. Du manuel bash :Fondamentalement, pour les valeurs non nulles non par défaut de
$IFS
, les champs peuvent être séparés avec (1) une séquence d'un ou plusieurs caractères qui font tous partie de l'ensemble des "espaces blancs IFS" (c'est-à-dire, selon <espace> , <tab> et <newline> ("newline" signifiant un saut de ligne (LF) ) sont présents n'importe où dans$IFS
), ou (2) tout autre "caractère d'espacement IFS" qui est présent$IFS
avec les "caractères d'espacement IFS" qui l'entourent dans la ligne d'entrée.Pour l'OP, il est possible que le deuxième mode de séparation que j'ai décrit dans le paragraphe précédent soit exactement ce qu'il veut pour sa chaîne d'entrée, mais nous pouvons être assez confiants que le premier mode de séparation que j'ai décrit n'est pas correct du tout. Par exemple, que faire si sa chaîne d'entrée était
'Los Angeles, United States, North America'
?2: Même si vous deviez utiliser cette solution avec un séparateur à caractère unique (comme une virgule par lui - même, qui est, sans espace suivant ou autres bagages), si la valeur de la
$string
variable de se contenir de lignocellulosiques, puisread
sera arrêtez le traitement une fois qu'il rencontre le premier LF. Le programmeread
intégré ne traite qu'une seule ligne par appel. Cela est vrai même si vous canalisez ou redirigez l'entrée uniquement vers l'read
instruction, comme nous le faisons dans cet exemple avec le mécanisme here-string , et donc l'entrée non traitée est garantie d'être perdue. Le code qui alimente la fonctionread
intégrée n'a aucune connaissance du flux de données au sein de sa structure de commande contenant.Vous pourriez faire valoir qu'il est peu probable que cela cause un problème, mais c'est quand même un danger subtil qui devrait être évité si possible. Cela est dû au fait que le
read
builtin fait en fait deux niveaux de division d'entrée: d'abord en lignes, puis en champs. Étant donné que l'OP ne souhaite qu'un seul niveau de fractionnement, cette utilisation de la fonctionread
intégrée n'est pas appropriée et nous devons l'éviter.3: Un problème potentiel non évident avec cette solution est que
read
le champ de fin est toujours supprimé s'il est vide, bien qu'il conserve les champs vides dans le cas contraire. Voici une démo:Peut-être que l'OP ne s'en soucierait pas, mais c'est toujours une limitation à connaître. Il réduit la robustesse et la généralité de la solution.
Ce problème peut être résolu en ajoutant un délimiteur de fin factice à la chaîne d'entrée juste avant de l'alimenter
read
, comme je le démontrerai plus tard.Mauvaise réponse # 2
Idée similaire:
(Remarque: j'ai ajouté les parenthèses manquantes autour de la substitution de commande que le répondeur semble avoir omis.)
Idée similaire:
Ces solutions exploitent la division des mots dans une affectation de tableau pour diviser la chaîne en champs. Curieusement, tout comme le
read
fractionnement de mots général utilise également la$IFS
variable spéciale, bien que dans ce cas, cela implique qu'il est défini sur sa valeur par défaut <espace><tab> <newline> , et donc toute séquence d'un ou plusieurs IFS Les caractères (qui sont désormais tous des espaces) sont considérés comme un délimiteur de champ.Cela résout le problème de deux niveaux de division commis par
read
, car la division de mot en elle-même ne constitue qu'un seul niveau de division. Mais tout comme auparavant, le problème ici est que les champs individuels de la chaîne d'entrée peuvent déjà contenir des$IFS
caractères, et donc ils seraient incorrectement divisés pendant l'opération de fractionnement de mots. Il se trouve que cela n'est le cas pour aucun des exemples de chaînes d'entrée fournies par ces répondeurs (comme c'est pratique ...), mais bien sûr, cela ne change pas le fait que toute base de code qui utilise cet idiome courrait alors le risque de exploser si cette hypothèse était jamais violée à un moment donné sur la ligne. Encore une fois, considérons mon contre-exemple de'Los Angeles, United States, North America'
(ou'Los Angeles:United States:North America'
).En outre, la séparation de mots est normalement suivi par extension de nom de fichier ( aka développement des chemins aka globbing), qui, si elle est faite, seraient des mots potentiellement corrompus contenant les caractères
*
,?
ou[
suivie de]
(et, le casextglob
est défini, fragments parenthesized précédé par?
,*
,+
,@
, ou!
) en les comparant aux objets du système de fichiers et en développant les mots ("globes") en conséquence. Le premier de ces trois répondeurs a habilement résolu ce problème en exécutantset -f
au préalable pour désactiver la globalisation. Techniquement, cela fonctionne (même si vous devriez probablement ajouterset +f
après pour réactiver la globalisation pour le code suivant qui peut en dépendre), mais il n'est pas souhaitable d'avoir à jouer avec les paramètres globaux du shell afin de pirater une opération d'analyse de base chaîne à tableau dans le code local.Un autre problème avec cette réponse est que tous les champs vides seront perdus. Cela peut ou non être un problème, selon l'application.
Remarque: Si vous allez utiliser cette solution, il vaut mieux utiliser la
${string//:/ }
forme "substitution de modèle" de l' expansion des paramètres , plutôt que de vous donner la peine d'invoquer une substitution de commande (qui bifurque le shell), de démarrer un pipeline, et exécuter un exécutable externe (tr
oused
), car l'expansion des paramètres est purement une opération interne au shell. (De plus, pour les solutionstr
etsed
, la variable d'entrée doit être placée entre guillemets à l'intérieur de la substitution de commande; sinon, le fractionnement de mots prendrait effet dans laecho
commande et risquerait de perturber les valeurs de champ. De plus, la$(...)
forme de substitution de commande est préférable à l'ancienne`...`
forme car il simplifie l'imbrication des substitutions de commandes et permet une meilleure mise en évidence de la syntaxe par les éditeurs de texte.)Mauvaise réponse # 3
Cette réponse est presque la même que # 2 . La différence est que le répondeur a fait l'hypothèse que les champs sont délimités par deux caractères, l'un étant représenté par défaut
$IFS
et l'autre non. Il a résolu ce cas assez spécifique en supprimant le caractère non représenté par IFS en utilisant une expansion de substitution de modèle, puis en utilisant la séparation de mots pour diviser les champs sur le caractère de délimiteur représenté par IFS survivant.Ce n'est pas une solution très générique. En outre, on peut faire valoir que la virgule est vraiment le caractère de délimiteur "principal" ici, et que le supprimer, puis dépendre du caractère d'espace pour le fractionnement de champ, est tout simplement faux. Encore une fois, pensez à mes contre - :
'Los Angeles, United States, North America'
.De plus, encore une fois, l'expansion du nom de fichier pourrait corrompre les mots développés, mais cela peut être évité en désactivant temporairement la globalisation pour l'affectation avec
set -f
puisset +f
.De plus, encore une fois, tous les champs vides seront perdus, ce qui peut ou non être un problème selon l'application.
Mauvaise réponse # 4
Ceci est similaire aux # 2 et # 3 dans la mesure où il utilise la séparation des mots pour faire le travail, seulement maintenant le code définit explicitement
$IFS
pour ne contenir que le délimiteur de champ à un caractère présent dans la chaîne d'entrée. Il convient de répéter que cela ne peut pas fonctionner pour les délimiteurs de champ à caractères multiples tels que le délimiteur d'espace virgule de l'OP. Mais pour un délimiteur à un seul caractère comme le LF utilisé dans cet exemple, il est presque parfait. Les champs ne peuvent pas être involontairement divisés au milieu, comme nous l'avons vu avec les mauvaises réponses précédentes, et il n'y a qu'un seul niveau de fractionnement, comme requis.Un problème est que l'expansion du nom de fichier corrompra les mots affectés comme décrit précédemment, bien qu'une fois encore cela puisse être résolu en encapsulant l'instruction critique dans
set -f
etset +f
.Un autre problème potentiel est que, puisque LF se qualifie comme un "caractère d'espace blanc IFS" tel que défini précédemment, tous les champs vides seront perdus, tout comme dans # 2 et # 3 . Cela ne serait bien sûr pas un problème si le délimiteur se trouve être un non-"caractère d'espacement IFS", et selon l'application, cela peut ne pas avoir d'importance de toute façon, mais cela vicie la généralité de la solution.
Donc, pour résumer, en supposant que vous avez un délimiteur à un caractère, et que ce soit un non-"caractère blanc IFS" ou vous ne vous souciez pas des champs vides, et vous encapsulez l'instruction critique dans
set -f
etset +f
, puis cette solution fonctionne , mais sinon non.(De plus, à titre d'information, l'attribution d'un LF à une variable dans bash peut être effectuée plus facilement avec la
$'...'
syntaxe, par exempleIFS=$'\n';
.)Mauvaise réponse # 5
Idée similaire:
Cette solution est en fait un croisement entre # 1 (en ce sens qu'elle se définit
$IFS
sur virgule) et # 2-4 (en ce qu'elle utilise la séparation de mots pour diviser la chaîne en champs). Pour cette raison, il souffre de la plupart des problèmes qui affligent toutes les mauvaises réponses ci-dessus, un peu comme le pire de tous les mondes.En outre, en ce qui concerne la deuxième variante, il peut sembler que l'
eval
appel est complètement inutile, car son argument est un littéral de chaîne entre guillemets simples et est donc statiquement connu. Mais il y a en fait un avantage très non évident à utilisereval
de cette façon. Normalement, lorsque vous exécutez une commande simple qui consiste en une affectation de variable uniquement , c'est-à-dire sans un mot de commande réel qui la suit, l'affectation prend effet dans l'environnement shell:Cela est vrai même si la commande simple implique plusieurs affectations de variables; encore une fois, tant qu'il n'y a pas de mot de commande, toutes les affectations de variables affectent l'environnement du shell:
Mais, si l'affectation de variable est attachée à un nom de commande (j'aime appeler cela une "affectation de préfixe"), cela n'affecte pas l'environnement shell, et affecte uniquement l'environnement de la commande exécutée, qu'il s'agisse d'une commande intégrée ou non ou externe:
Citation pertinente du manuel bash :
Il est possible d'exploiter cette fonctionnalité d'affectation de variable pour
$IFS
ne changer que temporairement, ce qui nous permet d'éviter tout le gambit de sauvegarde et de restauration comme celui qui est fait avec la$OIFS
variable dans la première variante. Mais le défi auquel nous sommes confrontés ici est que la commande que nous devons exécuter est en soi une simple affectation de variable, et donc elle n'impliquerait pas un mot de commande pour rendre l'$IFS
affectation temporaire. Vous pourriez penser à vous-même, eh bien pourquoi ne pas simplement ajouter un mot de commande sans opération à la déclaration comme le: builtin
pour rendre l'$IFS
affectation temporaire? Cela ne fonctionne pas car cela rendrait également l'$array
affectation temporaire:Donc, nous sommes effectivement dans une impasse, un peu un catch-22. Mais, lorsqu'il
eval
exécute son code, il l'exécute dans l'environnement shell, comme s'il s'agissait d'un code source statique normal, et nous pouvons donc exécuter l'$array
affectation à l'intérieur de l'eval
argument pour qu'il prenne effet dans l'environnement shell, tandis que l'$IFS
affectation de préfixe qui est préfixé à laeval
commande ne survivra pas à laeval
commande. C'est exactement l'astuce qui est utilisée dans la deuxième variante de cette solution:Donc, comme vous pouvez le voir, c'est en fait une astuce assez intelligente, et accomplit exactement ce qui est requis (au moins en ce qui concerne la mise en œuvre des affectations) d'une manière plutôt non évidente. Je ne suis en fait pas contre cette astuce en général, malgré l'implication de
eval
; faites juste attention à ne citer que la chaîne d'arguments pour vous prémunir contre les menaces de sécurité.Mais encore une fois, en raison de l'agglomération de problèmes "les pires de tous les mondes", il s'agit toujours d'une mauvaise réponse à l'exigence du PO.
Mauvaise réponse # 6
Euh, quoi? L'OP a une variable de chaîne qui doit être analysée dans un tableau. Cette «réponse» commence par le contenu textuel de la chaîne d'entrée collée dans un littéral de tableau. Je suppose que c'est une façon de procéder.
Il semble que le répondeur ait pu supposer que la
$IFS
variable affecte toutes les analyses bash dans tous les contextes, ce qui n'est pas vrai. Du manuel bash:Ainsi, la
$IFS
variable spéciale n'est en fait utilisée que dans deux contextes: (1) le fractionnement de mots qui est effectué après l'expansion (ce qui ne signifie pas lors de l'analyse du code source bash) et (2) pour le fractionnement des lignes d'entrée en mots par leread
code intégré.Permettez-moi de clarifier les choses. Je pense qu'il pourrait être bon de faire une distinction entre l' analyse et l' exécution . Bash doit d'abord analyser le code source, qui est évidemment un événement d' analyse , puis il exécute le code, c'est-à-dire lorsque l'expansion apparaît dans l'image. L'expansion est vraiment un événement d' exécution . De plus, je conteste la description de la
$IFS
variable que je viens de citer ci-dessus; plutôt que de dire que le fractionnement de mots est effectué après l'expansion , je dirais que le fractionnement de mots est effectué pendant l' expansion, ou, peut-être encore plus précisément, le fractionnement de mots fait partie dele processus d'expansion. L'expression "séparation des mots" se réfère uniquement à cette étape d'expansion; il ne devrait jamais être utilisé pour faire référence à l'analyse du code source de bash, bien que malheureusement les documents semblent beaucoup contourner les mots "split" et "words". Voici un extrait pertinent de la version linux.die.net du manuel bash:Vous pourriez dire que la version GNU du manuel fait un peu mieux, car elle opte pour le mot "jetons" au lieu de "mots" dans la première phrase de la section Expansion:
Le point important est,
$IFS
ne change pas la façon dont bash analyse le code source. L'analyse du code source bash est en fait un processus très complexe qui implique la reconnaissance des divers éléments de la grammaire du shell, tels que les séquences de commandes, les listes de commandes, les pipelines, les extensions de paramètres, les substitutions arithmétiques et les substitutions de commandes. Pour la plupart, le processus d'analyse bash ne peut pas être modifié par des actions au niveau de l'utilisateur comme les affectations de variables (en fait, il y a quelques exceptions mineures à cette règle; par exemple, voir les différentscompatxx
paramètres du shell, ce qui peut modifier certains aspects du comportement d'analyse à la volée). Les "mots" / "jetons" en amont qui résultent de ce processus d'analyse complexe sont ensuite développés selon le processus général d '"expansion" tel que décomposé dans les extraits de documentation ci-dessus, où la division des mots du texte développé (en expansion?) En aval les mots ne sont qu'une étape de ce processus. Le fractionnement de mots ne touche que le texte qui a été recraché lors d'une étape d'expansion précédente; cela n'affecte pas le texte littéral qui a été analysé directement à partir du flux source source.Mauvaise réponse # 7
C'est l'une des meilleures solutions. Notez que nous recommençons à utiliser
read
. N'avais-je pas dit plus tôt queread
c'était inapproprié parce qu'il effectuait deux niveaux de fractionnement, alors que nous n'en avions besoin que d'un? L'astuce ici est que vous pouvez appelerread
de manière à ce qu'il ne fasse effectivement qu'un seul niveau de fractionnement, en particulier en séparant un seul champ par appel, ce qui nécessite le coût de devoir l'appeler à plusieurs reprises dans une boucle. C'est un peu un tour de passe-passe, mais ça marche.Mais il y a des problèmes. Premièrement: lorsque vous fournissez au moins un argument NAME à
read
, il ignore automatiquement les espaces blancs de début et de fin dans chaque champ qui est séparé de la chaîne d'entrée. Cela se produit, que$IFS
sa valeur par défaut soit définie ou non, comme décrit plus haut dans cet article. Maintenant, l'OP peut ne pas se soucier de cela pour son cas d'utilisation spécifique, et en fait, cela peut être une caractéristique souhaitable du comportement d'analyse. Mais tous ceux qui veulent analyser une chaîne dans des champs ne le voudront pas. Il existe cependant une solution: une utilisation quelque peu non évidente deread
est de passer zéro argument NAME . Dans ce cas,read
stockera la ligne d'entrée entière qu'il obtient à partir du flux d'entrée dans une variable nommée$REPLY
, et, en prime, il ne passupprimer les espaces de début et de fin de la valeur. C'est une utilisation très robusteread
dont j'ai fréquemment exploité au cours de ma carrière en programmation shell. Voici une démonstration de la différence de comportement:Le deuxième problème avec cette solution est qu'elle ne traite pas réellement le cas d'un séparateur de champ personnalisé, tel que l'espace virgule de l'OP. Comme précédemment, les séparateurs multicaractères ne sont pas pris en charge, ce qui est une malheureuse limitation de cette solution. Nous pourrions essayer au moins de diviser la virgule en spécifiant le séparateur de l'
-d
option, mais regardez ce qui se passe:Comme on pouvait s'y attendre, l'espace blanc environnant non comptabilisé a été tiré dans les valeurs de champ, et donc cela devrait être corrigé par la suite par des opérations de découpage (cela pourrait également être fait directement dans la boucle while). Mais il y a une autre erreur évidente: l'Europe manque! Qu'est-ce qui lui est arrivé? La réponse est que
read
retourne un code retour défaillant s'il atteint la fin du fichier (dans ce cas, nous pouvons l'appeler fin de chaîne) sans rencontrer de terminateur de champ final sur le champ final. Cela provoque une rupture prématurée de la boucle while et nous perdons le champ final.Techniquement, cette même erreur a également frappé les exemples précédents; la différence est que le séparateur de champ a été pris pour être LF, ce qui est la valeur par défaut lorsque vous ne spécifiez pas l'
-d
option, et le<<<
mécanisme ("ici-chaîne") ajoute automatiquement un LF à la chaîne juste avant de l'alimenter en tant que entrée à la commande. Par conséquent, dans ces cas, nous avons en quelque sorte résolu accidentellement le problème d'un champ final abandonné en ajoutant involontairement un terminateur factice supplémentaire à l'entrée. Appelons cette solution la solution "terminateur factice". Nous pouvons appliquer la solution de terminaison factice manuellement pour tout délimiteur personnalisé en la concaténant nous-mêmes par rapport à la chaîne d'entrée lorsque nous l'instancions dans la chaîne ici:Là, problème résolu. Une autre solution consiste à ne rompre la boucle while que si (1) a
read
renvoyé un échec et (2)$REPLY
est vide, ce qui signifie qu'ilread
n'a pas été en mesure de lire les caractères avant d'appuyer sur la fin du fichier. Démo:Cette approche révèle également le LF secret qui est automatiquement ajouté à la chaîne ici par l'
<<<
opérateur de redirection. Il pourrait bien sûr être supprimé séparément via une opération de découpage explicite comme décrit il y a un instant, mais évidemment l'approche manuelle de terminaison factice le résout directement, nous pourrions donc simplement y aller. La solution manuelle de terminaison factice est en fait assez pratique en ce qu'elle résout ces deux problèmes (le problème du champ final abandonné et le problème LF ajouté) en une seule fois.Donc, dans l'ensemble, c'est une solution assez puissante. La seule faiblesse qui subsiste est le manque de prise en charge des délimiteurs multicaractères, que j'aborderai plus tard.
Mauvaise réponse # 8
(Il s'agit en fait du même message que # 7 ; le répondeur a fourni deux solutions dans le même message.)
Le
readarray
builtin, qui est synonyme demapfile
, est idéal. C'est une commande intégrée qui analyse un bytestream en une variable de tableau en une seule fois; pas de problème avec les boucles, les conditions, les substitutions ou autre chose. Et il ne supprime subrepticement aucun espace de la chaîne d'entrée. Et (s'il-O
n'est pas indiqué), il efface commodément le tableau cible avant de lui être affecté. Mais ce n'est pas encore parfait, d'où ma critique de cela comme une "mauvaise réponse".Tout d'abord, pour éviter cela, notez que, tout comme le comportement de l'
read
analyse de champ,readarray
supprime le champ de fin s'il est vide. Encore une fois, ce n'est probablement pas une préoccupation pour le PO, mais cela pourrait l'être pour certains cas d'utilisation. J'y reviendrai dans un instant.Deuxièmement, comme précédemment, il ne prend pas en charge les délimiteurs multicaractères. Je vais également vous donner une solution dans un instant.
Troisièmement, la solution telle qu'elle est écrite n'analyse pas la chaîne d'entrée de l'OP et, en fait, elle ne peut pas être utilisée telle quelle pour l'analyser. Je vais m'étendre là-dessus aussi momentanément.
Pour les raisons ci-dessus, je considère toujours qu'il s'agit d'une "mauvaise réponse" à la question du PO. Ci-dessous, je donnerai ce que je considère être la bonne réponse.
Bonne réponse
Voici une tentative naïve de faire fonctionner # 8 en spécifiant simplement l'
-d
option:Nous voyons que le résultat est identique au résultat que nous avons obtenu de l'approche double conditionnelle de la
read
solution de bouclage discutée dans # 7 . Nous pouvons presque résoudre ce problème avec l'astuce de terminaison factice manuelle:Le problème ici est qu'il a
readarray
conservé le champ de fin, car l'<<<
opérateur de redirection a ajouté le LF à la chaîne d'entrée, et donc le champ de fin n'était pas vide (sinon il aurait été supprimé). Nous pouvons nous en occuper en supprimant explicitement l'élément final du tableau après coup:Les deux seuls problèmes qui restent, qui sont en fait liés, sont (1) l'espace blanc étranger qui doit être coupé, et (2) le manque de prise en charge des délimiteurs multicaractères.
L'espace blanc pourrait bien sûr être coupé par la suite (par exemple, voir Comment découper un espace blanc à partir d'une variable Bash? ). Mais si nous pouvons pirater un délimiteur multicaractère, cela résoudrait les deux problèmes en une seule fois.
Malheureusement, il n'existe aucun moyen direct de faire fonctionner un délimiteur multicaractère. La meilleure solution à laquelle j'ai pensé est de prétraiter la chaîne d'entrée pour remplacer le délimiteur multicaractère par un délimiteur à un caractère qui sera garanti de ne pas entrer en collision avec le contenu de la chaîne d'entrée. Le seul caractère qui a cette garantie est l' octet NUL . En effet, dans bash (mais pas dans zsh, d'ailleurs), les variables ne peuvent pas contenir l'octet NUL. Cette étape de prétraitement peut être effectuée en ligne dans une substitution de processus. Voici comment le faire en utilisant awk :
Voilà enfin! Cette solution ne divisera pas par erreur les champs au milieu, ne se coupera pas prématurément, ne supprimera pas les champs vides, ne se corrompra pas sur les extensions de nom de fichier, ne supprimera pas automatiquement les espaces blancs de début et de fin, ne laissera pas de LF clandestin à la fin, ne nécessite pas de boucles et ne se contente pas d'un délimiteur à un caractère.
Solution de coupe
Enfin, je voulais démontrer ma propre solution de découpage assez complexe en utilisant l'
-C callback
option obscure dereadarray
. Malheureusement, je n'ai plus de place contre la limite de publication draconienne de 30 000 caractères de Stack Overflow, donc je ne serai pas en mesure de l'expliquer. Je vais laisser cela comme un exercice pour le lecteur.la source
-d
option d'readarray
apparaître en premier dans Bash 4.4.awk '{ gsub(/,[ ]+|$/,"\0"); print }'
et éliminez cette concaténation de la finale,", "
vous n'avez pas à passer par la gymnastique pour éliminer le record final. Donc:readarray -td '' a < <(awk '{ gsub(/,[ ]+/,"\0"); print; }' <<<"$string")
sur Bash qui prend en chargereadarray
. Notez votre méthode est Bash 4.4+ Je pense à cause de la-d
dansreadarray
readarray
. Dans ce cas, vous pouvez utiliser la deuxième meilleure solution basée surread
. Je fais référence à ceci:a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,";
(avec laawk
substitution si vous avez besoin d'un support de délimiteur multicaractère). Faites-moi savoir si vous rencontrez des problèmes; Je suis presque sûr que cette solution devrait fonctionner sur des versions assez anciennes de bash, de retour à la version 2-quelque chose, sortie il y a deux décennies.Voici un moyen sans définir IFS:
L'idée est d'utiliser le remplacement de chaîne:
pour remplacer toutes les correspondances de $ substring par un espace blanc, puis en utilisant la chaîne substituée pour initialiser un tableau:
Remarque: cette réponse utilise l' opérateur split + glob . Ainsi, pour empêcher l'expansion de certains caractères (tels que
*
), il est judicieux de suspendre la globalisation de ce script.la source
${string//:/ }
empêche l'expansion du shellarray=(${string//:/ })
Imprime trois
la source
a=($(echo $t | tr ',' "\n"))
. Même résultat aveca=($(echo $t | tr ',' ' '))
.VERSION="16.04.2 LTS (Xenial Xerus)"
dans unbash
shell, et le dernierecho
imprime juste une ligne vierge. Quelle version de Linux et quel shell utilisez-vous? Malheureusement, impossible d'afficher la session de terminal dans un commentaire.Parfois, il m'est arrivé que la méthode décrite dans la réponse acceptée ne fonctionne pas, surtout si le séparateur est un retour chariot.
Dans ces cas, j'ai résolu de cette façon:
la source
read -a arr <<< "$strings"
n'ai pas travaillé avecIFS=$'\n'
.La réponse acceptée fonctionne pour les valeurs sur une seule ligne.
Si la variable a plusieurs lignes:
Nous avons besoin d'une commande très différente pour obtenir toutes les lignes:
while read -r line; do lines+=("$line"); done <<<"$string"
Ou le tableau de lecture bash beaucoup plus simple :
L'impression de toutes les lignes est très facile en profitant d'une fonction printf:
la source
Ceci est similaire à l' approche de Jmoney38 , mais en utilisant sed:
Impressions 1
la source
La clé pour diviser votre chaîne en un tableau est le délimiteur multi-caractères de
", "
. Toute solution utilisantIFS
des délimiteurs à plusieurs caractères est intrinsèquement erronée car IFS est un ensemble de ces caractères, pas une chaîne.Si vous attribuez
IFS=", "
alors la chaîne se brisera sur","
OU OU sur" "
toute combinaison d'entre eux qui n'est pas une représentation précise du délimiteur à deux caractères de", "
.Vous pouvez utiliser
awk
oused
pour diviser la chaîne, avec substitution de processus:Il est plus efficace d'utiliser une expression régulière directement dans Bash:
Avec le second formulaire, il n'y a pas de sous-shell et ce sera intrinsèquement plus rapide.
Edit by bgoldst: Voici quelques repères comparant ma
readarray
solution à la solution regex de dawg, et j'ai également inclus laread
solution pour le diable (note: j'ai légèrement modifié la solution regex pour une plus grande harmonie avec ma solution) (voir aussi mes commentaires ci-dessous le Publier):la source
$BASH_REMATCH
. Cela fonctionne, et évite en effet de créer des sous-coquilles. +1 de moi. Cependant, à titre de critique, l'expression régulière elle-même est un peu non idéale, dans la mesure où il semble que vous ayez été obligé de dupliquer une partie du jeton de délimitation (en particulier la virgule) afin de contourner le manque de support pour les multiplicateurs non gourmands. (également des contournements) dans ERE (saveur regex "étendue" intégrée dans bash). Cela le rend un peu moins générique et robuste.\n
lignes de texte délimitées) comprenant ces champs, de sorte que le ralentissement catastrophique ne se produirait probablement pas. Si vous avez une chaîne avec 100 000 champs - peut-être que Bash n'est pas idéal ;-) Merci pour le benchmark. J'ai appris une chose ou deux.Solution de délimiteur multi-caractères Pure Bash.
Comme d'autres l'ont souligné dans ce fil, la question du PO a donné un exemple de chaîne délimitée par des virgules à analyser dans un tableau, mais n'a pas indiqué s'il était uniquement intéressé par les délimiteurs par des virgules, les délimiteurs à caractère unique ou à plusieurs caractères délimiteurs.
Étant donné que Google a tendance à classer cette réponse en haut ou près du haut des résultats de recherche, je voulais fournir aux lecteurs une réponse forte à la question des délimiteurs à plusieurs caractères, car cela est également mentionné dans au moins une réponse.
Si vous êtes à la recherche d'une solution à un problème de délimiteur multi-caractères, je suggère de revoir le post de Mallikarjun M , en particulier la réponse de gniourf_gniourf qui fournit cette élégante solution BASH pure en utilisant l'expansion des paramètres:
Lien vers le commentaire cité / l'article référencé
Lien vers la question citée: comment diviser une chaîne sur un délimiteur à plusieurs caractères en bash?
la source
Cela fonctionne pour moi sur OSX:
Si votre chaîne a un délimiteur différent, remplacez d'abord ceux par de l'espace:
Facile :-)
la source
Une autre façon de le faire sans modifier IFS:
Plutôt que de changer IFS pour correspondre à notre délimiteur souhaité, nous pouvons remplacer toutes les occurrences de notre délimiteur souhaité
", "
par le contenu de$IFS
via"${string//, /$IFS}"
.Peut-être que ce sera lent pour les très grandes chaînes?
Ceci est basé sur la réponse de Dennis Williamson.
la source
Je suis tombé sur ce post en cherchant à analyser une entrée comme: word1, word2, ...
rien de ce qui précède ne m'a aidé. résolu en utilisant awk. Si cela aide quelqu'un:
la source
Essaye ça
C'est simple. Si vous le souhaitez, vous pouvez également ajouter une déclaration (et également supprimer les virgules):
L'IFS est ajouté pour annuler ce qui précède, mais il fonctionne sans dans une nouvelle instance bash
la source
Nous pouvons utiliser la commande tr pour diviser la chaîne en objet tableau. Il fonctionne à la fois MacOS et Linux
Une autre option utilise la commande IFS
la source
Utilisez ceci:
la source
array=( $string )
un (malheureusement très fréquent) anti -modèle : la séparation de mots se produit:string='Prague, Czech Republic, Europe'
; L'expansion du nom de chemin se produit:string='foo[abcd],bar[efgh]'
échouera si vous avez un fichier nommé, par exemple,food
oubarf
dans votre répertoire. La seule utilisation valide d'une telle construction est quandstring
est un glob.MISE À JOUR: Ne faites pas cela, en raison de problèmes avec eval.
Avec un peu moins de cérémonie:
par exemple
la source
$
dans votre variable et vous verrez ... J'écris de nombreux scripts et je n'ai jamais eu à en utiliser un seuleval
Voici mon hack!
Le fractionnement de chaînes par chaînes est une chose assez ennuyeuse à faire en utilisant bash. Ce qui se passe, c'est que nous avons des approches limitées qui ne fonctionnent que dans quelques cas (divisés par ";", "/", "." Et ainsi de suite) ou nous avons une variété d'effets secondaires dans les sorties.
L'approche ci-dessous a nécessité un certain nombre de manœuvres, mais je crois qu'elle fonctionnera pour la plupart de nos besoins!
la source
Pour les éléments multilignés, pourquoi pas quelque chose comme
la source
Une autre façon serait:
Vos éléments sont maintenant stockés dans le tableau "arr". Pour parcourir les éléments:
la source
eval
astuce). Votre solution laisse$IFS
la valeur de la virgule après le fait.Puisqu'il existe de nombreuses façons de résoudre ce problème, commençons par définir ce que nous voulons voir dans notre solution.
readarray
à cet effet. Utilisons-le.IFS
, boucler, utilisereval
ou ajouter un élément supplémentaire puis le supprimer.La
readarray
commande est plus facile à utiliser avec des retours à la ligne comme délimiteur. Avec d'autres délimiteurs, il peut ajouter un élément supplémentaire au tableau. L'approche la plus propre consiste à adapter d'abord notre entrée dans un formulaire qui fonctionne bienreadarray
avant de le transmettre.L'entrée dans cet exemple n'a pas de délimiteur multicaractère. Si nous appliquons un peu de bon sens, il est préférable de comprendre une entrée séparée par des virgules pour laquelle chaque élément peut avoir besoin d'être coupé. Ma solution est de diviser l'entrée par virgule en plusieurs lignes, de découper chaque élément et de le transmettre à
readarray
.la source
Une autre approche peut être:
Après cet «arr» se trouve un tableau avec quatre chaînes. Cela ne nécessite pas de traiter IFS ou de lire ou tout autre élément spécial, donc beaucoup plus simple et direct.
la source