L'analyse d'un tableau à l'aide d'IFS avec des valeurs d'espace non blanches crée des éléments vides.
Même utiliser tr -s
pour réduire plusieurs délimitations à une seule délimitation n'est pas suffisant.
Un exemple peut expliquer le problème plus clairement.
Existe - t-il un moyen d'obtenir des résultats "normaux" via une modification d'IFS (y a-t-il un paramètre associé pour changer le comportement d'IFS? .... ie. Pour agir de la même manière que l'espace blanc par défaut IFS.
var=" abc def ghi "
echo "============== IFS=<default>"
arr=($var)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
#
sfi="$IFS" ; IFS=':'
set -f # Disable file name generation (globbing)
# (This data won't "glob", but unless globbing
# is actually needed, turn if off, because
# unusual/unexpected combinations of data can glob!
# and they can do it in the most obscure ways...
# With IFS, "you're not in Kansas any more! :)
var=":abc::def:::ghi::::"
echo "============== IFS=$IFS"
arr=($var)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
echo "============== IFS=$IFS and tr"
arr=($(echo -n "$var"|tr -s "$IFS"))
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
set +f # enable globbing
IFS="$sfi" # re-instate original IFS val
echo "============== IFS=<default>"
Voici la sortie
============== IFS=<default>
# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"
============== IFS=:
# arr[0] ""
# arr[1] "abc"
# arr[2] ""
# arr[3] "def"
# arr[4] ""
# arr[5] ""
# arr[6] "ghi"
# arr[7] ""
# arr[8] ""
# arr[9] ""
============== IFS=: and tr
# arr[0] ""
# arr[1] "abc"
# arr[2] "def"
# arr[3] "ghi"
============== IFS=<default>
Réponses:
Pour supprimer plusieurs caractères de délimiteur consécutifs (sans espace), deux extensions de paramètres (chaîne / tableau) peuvent être utilisées. L'astuce consiste à définir la
IFS
variable sur la chaîne vide pour l'expansion des paramètres du tableau.Ceci est documenté dans
man bash
sous Word Splitting :la source
IFS=' '
(c'est-à-dire un espace) se comporte de la même manière. Je trouve cela moins déroutant qu'un argument nul explicite ("" ou '') deIFS
.Depuis la
bash
page de manuel:Cela signifie que les espaces blancs IFS (espace, tabulation et nouvelle ligne) ne sont pas traités comme les autres séparateurs. Si vous souhaitez obtenir exactement le même comportement avec un séparateur alternatif, vous pouvez effectuer un échange de séparateur à l'aide de
tr
oused
:La
%#%#%#%#%
chose est une valeur magique pour remplacer les espaces possibles à l'intérieur des champs, elle devrait être "unique" (ou très peu liée). Si vous êtes sûr qu'aucun espace ne sera jamais dans les champs, laissez tomber cette partie).la source
tr
exemples pour montrer le problème ... Je veux éviter un appel système, donc je vais regarder une option bash au-delà de celle${var##:}
que j'ai mentionnée dans mon commentaire à la réponse de glen .... Je vais attendre un moment .. peut-être qu'il y a un moyen d'amadouer IFS, sinon la première partie de votre réponse est après ...IFS
est le même dans tous les shells de style Bourne, il est spécifié dans POSIX .IFS
caractères comme chaîne de délimitation. Ma question a été mieux répondue parjon_d
, mais la réponse de @ nazad montre une manière astucieuse à utiliserIFS
sans boucles et sans applications utilitaires.Étant donné que bash IFS ne fournit pas de méthode interne pour traiter les caractères de délimiteur consécutifs comme un seul délimiteur (pour les délimiteurs non blancs), j'ai mis au point une version tout bash (par opposition à l'utilisation d'un appel externe, par exemple tr, awk, sed )
Il peut gérer les IFS multi-caractères.
Voici ses résultats au moment de l'exécution, ainsi que des tests similaires pour les options
tr
etawk
affichées sur cette page Q / A ... Les tests sont basés sur 10000 itérations de construction du tableau (sans E / S) ...Voici la sortie
Voici le script
la source
Vous pouvez aussi le faire avec gawk, mais ce n'est pas joli:
les sorties
la source
$var
en${var##:}
... Je cherchais vraiment un moyen de modifier IFS lui-même .. Je veux pour le faire sans appel externe (j'ai le sentiment que bash peut le faire plus efficacement que n'importe quel externe .. alors je vais continuer sur cette voie) ... votre méthode fonctionne (+1) .... Pour autant que comme la modification de l'entrée va, je préfère l'essayer avec bash, plutôt qu'avec awk ou tr (cela éviterait un appel système), mais je suis vraiment en train de traîner pour un tweak IFS ...bash 1.276s
...call (awk) 0m32.210s
,,,call (tr) 0m32.178s
... Faites cela plusieurs fois et vous pourriez penser que bash est lent! ... est awk plus facile dans ce cas? ... pas si vous avez déjà l'extrait :) ... Je le posterai plus tard; je dois y aller maintenant.var="The \"X\" factor:::A single '\"' crashes:::\"One Two\""
La réponse simple est: réduire tous les délimiteurs à un (le premier).
Cela nécessite une boucle (qui s'exécute moins de
log(N)
fois):Il ne reste plus qu'à diviser correctement la chaîne sur un délimiteur et à l'imprimer:
Pas besoin
set -f
ni de changer IFS.Testé avec des espaces, des nouvelles lignes et des caractères globaux. Tout le travail. Assez lent (comme devrait l'être une boucle shell).
Mais uniquement pour bash (bash 4.4+ en raison de l'option
-d
de readarray).sh
Une version shell ne peut pas utiliser un tableau, le seul tableau disponible sont les paramètres positionnels.
L'utilisation
tr -s
n'est qu'une ligne (IFS ne change pas dans le script):Et imprimez-le:
Toujours lent, mais pas beaucoup plus.
La commande
command
n'est pas valide dans Bourne.Dans zsh,
command
appelle uniquement les commandes externes et fait échouer eval sicommand
est utilisé.Dans ksh, même avec
command
, la valeur d'IFS est modifiée dans la portée globale.Et
command
fait échouer le fractionnement dans les shells liés à mksh (mksh, lksh, posh) La suppression de la commandecommand
fait exécuter le code sur plus de shells. Mais: la suppressioncommand
fera que IFS conservera sa valeur dans la plupart des shells (eval est une fonction spéciale) sauf en bash (sans mode posix) et zsh en mode par défaut (pas d'émulation). Ce concept ne peut pas fonctionner avec ou sans zsh par défautcommand
.IFS à plusieurs caractères
Oui, IFS peut être multi-caractères, mais chaque caractère génère un argument:
Sortira:
Avec bash, vous pouvez omettre le
command
mot s'il n'est pas dans l'émulation sh / POSIX. La commande échouera dans ksh93 (IFS conserve la valeur modifiée). Dans zsh, la commandecommand
oblige zsh à recherchereval
une commande externe (qu'elle ne trouve pas) et échoue.Ce qui se passe, c'est que les seuls caractères IFS qui sont automatiquement réduits à un délimiteur sont les espaces blancs IFS.
Un espace dans IFS réduira tous les espaces consécutifs en un seul. Un onglet réduira tous les onglets. Un espace et un onglet réduiront les séries d'espaces et / ou d'onglets dans un délimiteur. Répétez l'idée avec la nouvelle ligne.
Pour réduire plusieurs délimiteurs, une jonglerie est nécessaire.
En supposant que l'ASCII 3 (0x03) n'est pas utilisé dans l'entrée
var
:La plupart des commentaires sur ksh, zsh et bash (about
command
et IFS) s'appliquent toujours ici.Une valeur de
$'\0'
serait moins probable dans la saisie de texte, mais les variables bash ne peuvent pas contenir de NUL (0x00
).Il n'y a pas de commandes internes dans sh pour effectuer les mêmes opérations de chaîne, donc tr est la seule solution pour les scripts sh.
la source
command eval
IIRC par Gilles