J'hésite toujours beaucoup à jouer $IFS
parce que ça fait trembler un monde.
Mais souvent, cela rend le chargement des chaînes dans un tableau bash agréable et concis, et pour les scripts bash, la concision est difficile à trouver.
Je pense donc que cela pourrait être mieux que rien si j'essaie de «sauvegarder» le contenu de départ d' $IFS
une autre variable, puis de le restaurer immédiatement après avoir fini d'utiliser $IFS
quelque chose.
Est-ce pratique? Ou est-il essentiellement inutile et je devrais simplement IFS
revenir directement à ce qu'il doit être pour ses utilisations ultérieures?
bash
shell-script
Steven Lu
la source
la source
$' \t\n'
si vous utilisez bash.unset $IFS
ne le restaure pas toujours à ce que vous attendez d'être la valeur par défaut.Réponses:
Vous pouvez enregistrer et affecter à IFS selon vos besoins. Il n'y a rien de mal à le faire. Il n'est pas rare d'enregistrer sa valeur pour la restauration après une modification temporaire et rapide, comme votre exemple d'affectation de tableau.
Comme @llua le mentionne dans son commentaire à votre question, la simple suppression de IFS restaurera le comportement par défaut, ce qui équivaut à attribuer un espace-tab-newline.
Cela vaut la peine de considérer comment il peut être plus problématique de ne pas explicitement définir / désinstaller IFS que de le faire.
Depuis l'édition POSIX 2013, 2.5.3 Variables du shell :
Un shell invoqué compatible POSIX peut ou non hériter IFS de son environnement. De cela suit:
"$*"
), mais qui peut s'exécuter sous un shell qui initialise IFS à partir de l'environnement, doit explicitement définir / désactiver IFS pour se défendre contre les intrusions environnementales.NB Il est important de comprendre que pour cette discussion le mot "invoqué" a une signification particulière. Un shell n'est invoqué que lorsqu'il est explicitement appelé en utilisant son nom (y compris un
#!/path/to/shell
shebang). Un sous-shell - tel que pourrait être créé par$(...)
oucmd1 || cmd2 &
- n'est pas un shell invoqué, et son IFS (ainsi que la plupart de son environnement d'exécution) est identique à celui de son parent. Un shell invoqué définit la valeur de$
son pid, tandis que les sous-shell en héritent.Ce n'est pas simplement une disquisition pédante; il existe une réelle divergence dans ce domaine. Voici un bref script qui teste le scénario à l'aide de plusieurs shells différents. Il exporte un IFS modifié (défini sur
:
) vers un shell appelé qui imprime ensuite son IFS par défaut.IFS n'est généralement pas marqué pour l'exportation, mais s'il l'était, notez comment bash, ksh93 et mksh ignorent leur environnement
IFS=:
, tandis que dash et busybox l'honorent.Quelques informations de version:
Même si bash, ksh93 et mksh n'initialisent pas IFS à partir de l'environnement, ils réexportent leur IFS modifié.
Si, pour quelque raison que ce soit, vous devez transmettre IFS de manière portable via l'environnement, vous ne pouvez pas le faire en utilisant IFS lui-même; vous devrez affecter la valeur à une variable différente et marquer cette variable pour l'exportation. Les enfants devront ensuite attribuer explicitement cette valeur à leur IFS.
la source
IFS
valeur dans la plupart des situations où elle doit être utilisée, et donc il n'est souvent pas terriblement productif d'essayer même de "préserver" sa valeur d'origine.read
s ou des références entre guillemets$*
. Cette liste est juste au dessus de ma tête, donc elle peut ne pas être complète (surtout si l'on considère les extensions POSIX des shells modernes).En règle générale, il est recommandé de rétablir les conditions par défaut.
Cependant, dans ce cas, pas tellement.
Pourquoi?:
$' \t\n'
.unset IFS
fait de l' exécuter fait comme s'il était réglé par défaut .En outre, le stockage de la valeur IFS a un problème.
Si l'IFS d'origine n'a pas été défini, le code
IFS="$OldIFS"
définira IFS sur""
, et non pas le désactiver.Pour conserver réellement la valeur de IFS (même si non définie), utilisez ceci:
la source
bash
,unset IFS
ne parvient pas à désactiver IFS s'il a été déclaré local dans un contexte parent (contexte de fonction) et non dans le contexte actuel.Vous avez raison d'hésiter à secouer un monde. N'ayez crainte, il est possible d'écrire du code de travail propre sans jamais modifier le global réel
IFS
, ou faire une danse de sauvegarde / restauration lourde et sujette aux erreurs.Vous pouvez:
définissez IFS pour une seule invocation:
ou
définir IFS dans un sous-shell:
Exemples
Pour obtenir une chaîne séparée par des virgules à partir d'un tableau:
Remarque: Le
-
est uniquement destiné à protéger un tableau vide contreset -u
en fournissant une valeur par défaut lorsqu'il n'est pas défini (cette valeur étant la chaîne vide dans ce cas) .La
IFS
modification n'est applicable qu'à l'intérieur du sous-shell engendré par la$()
substitution de commande . C'est parce que les sous-coquilles ont des copies des variables du shell appelant et peuvent donc lire leurs valeurs, mais toutes les modifications effectuées par la sous-coquille n'affectent que la copie de la sous-coquille et non la variable du parent.Vous pourriez également penser: pourquoi ne pas sauter le sous-shell et faire juste ceci:
Il n'y a pas d'invocation de commande ici, et cette ligne est plutôt interprétée comme deux affectations de variables ultérieures indépendantes, comme si elle l'était:
Enfin, expliquons pourquoi cette variante ne fonctionnera pas:
La
echo
commande sera en effet appelée avec saIFS
variable définie sur,
, maisecho
ne s'en soucie ni ne l'utiliseIFS
. La magie de l'extension"${array[*]}"
à une chaîne est effectuée par le (sous-) shell lui-même avantecho
même d'être invoquée.Pour lire un fichier entier (qui ne contient pas d'
NULL
octets) dans une seule variable nomméeVAR
:Remarque:
IFS=
est identique àIFS=""
etIFS=''
, qui définissent tous IFS sur la chaîne vide, ce qui est très différent deunset IFS
: siIFS
n'est pas défini, le comportement de toutes les fonctionnalités bash qui utilisent en interneIFS
est exactement le même que s'ilIFS
avait la valeur par défaut de$' \t\n'
.La définition
IFS
de la chaîne vide garantit que les espaces de début et de fin sont préservés.Le
-d ''
ou-d ""
indique à read d'arrêter uniquement son appel en cours sur unNULL
octet, au lieu du retour à la ligne habituel.Pour diviser le
$PATH
long de ses:
délimiteurs:Cet exemple est purement illustratif. Dans le cas général où vous divisez le long d'un délimiteur, il est possible que les champs individuels contiennent (une version d'échappement de) ce délimiteur. Pensez à essayer de lire une ligne d'un
.csv
fichier dont les colonnes peuvent elles-mêmes contenir des virgules (échappées ou citées d'une manière ou d'une autre). L'extrait ci-dessus ne fonctionnera pas comme prévu dans de tels cas.Cela dit, il est peu probable que vous rencontriez de tels
:
chemins contenant$PATH
. Bien que les chemins d'accès UNIX / Linux soient autorisés à contenir un:
, il semble que bash ne serait pas en mesure de gérer de tels chemins de toute façon si vous essayez de les ajouter à votre$PATH
et d'y stocker des fichiers exécutables, car il n'y a pas de code pour analyser les deux-points échappés / cités. : code source de bash 4.4 .Enfin, notez que l'extrait ajoute une nouvelle ligne de fin au dernier élément du tableau résultant (comme l'a appelé @ StéphaneChazelas dans les commentaires maintenant supprimés), et que si l'entrée est la chaîne vide, la sortie sera un élément unique tableau, où l'élément sera constitué d'un saut de ligne (
$'\n'
).Motivation
L'
old_IFS="${IFS}"; command; IFS="${old_IFS}"
approche de base qui touche le globalIFS
fonctionnera comme prévu pour le plus simple des scripts. Cependant, dès que vous ajoutez une complexité, elle peut facilement se séparer et provoquer des problèmes subtils:command
est une fonction bash qui modifie également le globalIFS
(soit directement soit, à l'abri des regards, à l'intérieur d'une autre fonction qu'elle appelle), et ce faisant, utilise par erreur la mêmeold_IFS
variable globale pour effectuer la sauvegarde / restauration, vous obtenez un bogue.IFS
n'était pas défini, la sauvegarde et la restauration naïves ne fonctionneront pas, et entraîneront même des échecs purs et simples si l' option shell couramment (mal) utiliséeset -u
(akaset -o nounset
) est en vigueur.help trap
). Si ce code modifie également le globalIFS
ou suppose qu'il a une valeur particulière, vous pouvez obtenir des bogues subtils.Vous pouvez concevoir une séquence de sauvegarde / restauration plus robuste (comme celle proposée dans cette autre réponse pour éviter certains ou tous ces problèmes. Cependant, vous devrez répéter ce morceau de code passe-partout bruyant partout où vous avez temporairement besoin d'une personnalisation
IFS
. réduit la lisibilité et la maintenabilité du code.Considérations supplémentaires pour les scripts de type bibliothèque
IFS
est particulièrement une préoccupation pour les auteurs de bibliothèques de fonctions shell qui doivent s'assurer que leur code fonctionne de manière robuste quel que soit l'état global (IFS
, les options de shell, ...) imposé par leurs appelants, et aussi sans perturber cet état (les invocateurs peuvent s'appuyer sur dessus pour rester toujours statique).Lorsque vous écrivez du code de bibliothèque, vous ne pouvez pas compter sur
IFS
une valeur particulière (pas même celle par défaut) ou même être défini du tout. Au lieu de cela, vous devez définir explicitementIFS
pour tout extrait dont le comportement dépendIFS
.Si
IFS
est explicitement défini sur la valeur nécessaire (même si celle-ci se trouve être celle par défaut) dans chaque ligne de code où la valeur importe en utilisant l'un des deux mécanismes décrits dans cette réponse qui convient pour localiser l'effet, le code est à la fois indépendante de l’état mondial et évite de l’encombrer complètement. Cette approche a l'avantage supplémentaire de la rendre très explicite pour une personne lisant le script quiIFS
compte précisément pour cette commande / expansion à un coût textuel minimum (par rapport à même la sauvegarde / restauration la plus basique).De quel code est affecté de
IFS
toute façon?Heureusement, il n'y a pas beaucoup de scénarios où cela
IFS
compte (en supposant que vous citez toujours vos extensions ):"$*"
et"${array[*]}"
extensionsread
cible intégrée ciblant plusieurs variables (read VAR1 VAR2 VAR3
) ou une variable de tableau (read -a ARRAY_VAR_NAME
)read
ciblage d'une variable unique en ce qui concerne les espaces blancs de début / fin ou les espaces non blancs apparaissant dansIFS
.la source
:
quand:
est le délimiteur?:
c'est un caractère valide à utiliser dans un nom de fichier sur la plupart des systèmes de fichiers UNIX / Linux, il est donc tout à fait possible d'avoir un répertoire avec un nom contenant:
. Peut-être que certains shells ont une disposition pour s'échapper:
dans PATH en utilisant quelque chose comme\:
, et vous verrez alors apparaître des colonnes qui ne sont pas de véritables délimiteurs (Il semble que bash n'autorise pas un tel échappement. La fonction de bas niveau utilisée lors de l'itération sur$PATH
simplement recherche:
dans une chaîne C: git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891 ).$PATH
,:
clarifier l' exemple de fractionnement .Pourquoi risquer une faute de frappe sur IFS
$' \t\n'
alors que tout ce que vous avez à faire estAlternativement, vous pouvez appeler un sous-shell si vous n'avez pas besoin de variables définies / modifiées dans:
la source
IFS
n'était pas réglé au départ.