Je sais que je peux résoudre ce problème de plusieurs manières, mais je me demande s'il existe un moyen de le faire en utilisant uniquement les bash intégrés, et sinon, quel est le moyen le plus efficace de le faire.
J'ai un fichier avec un contenu comme
AAA
B C DDD
FOO BAR
par quoi je veux seulement dire qu'il a plusieurs lignes et que chaque ligne peut ou non avoir des espaces. Je veux exécuter une commande comme
cmd AAA "B C DDD" "FOO BAR"
Si j'utilise cmd $(< file)
je reçois
cmd AAA B C DDD FOO BAR
et si j'utilise cmd "$(< file)"
je reçois
cmd "AAA B C DDD FOO BAR"
Comment obtenir que chaque ligne soit traitée avec exactement un paramètre?
bash
command-substitution
Old Pro
la source
la source
Réponses:
Portablement:
Ou en utilisant un sous-shell pour rendre les
IFS
modifications de l'option et locales:Le shell effectue le fractionnement de champ et la génération de nom de fichier sur le résultat d'une substitution de variable ou de commande qui n'est pas entre guillemets. Vous devez donc désactiver la génération de nom de fichier avec
set -f
, et configurer la division des champs avecIFS
pour ne faire que des sauts de ligne séparés.Il n'y a pas grand-chose à gagner avec les constructions bash ou ksh. Vous pouvez rendre
IFS
local une fonction, mais passet -f
.Dans bash ou ksh93, vous pouvez stocker les champs dans un tableau, si vous devez les passer à plusieurs commandes. Vous devez contrôler l'expansion au moment de la création de la baie.
"${a[@]}"
S'étend ensuite aux éléments du tableau, un par mot.la source
Vous pouvez le faire avec un tableau temporaire.
Installer:
Remplissez le tableau:
Utilisez le tableau:
Impossible de trouver un moyen de le faire sans cette variable temporaire - sauf si le
IFS
changement n'est pas important pourcmd
, dans ce cas:devrait le faire.
la source
IFS
me rend toujours confus.IFS=$'\n' cmd $(<input)
ne fonctionne pas.IFS=$'\n'; cmd $(<input); unset IFS
fonctionne. Pourquoi? Je suppose que je vais utiliser(IFS=$'\n'; cmd $(<input))
IFS=$'\n' cmd $(<input)
ne fonctionne pas car il ne se définit queIFS
dans l'environnement decmd
.$(<input)
est développé pour former la commande, avant que l'affectation à neIFS
soit effectuée.On dirait que la manière canonique de le faire
bash
est quelque chose commeou, si votre version de bash a
mapfile
:La seule différence que je peux trouver entre le mapfile et la boucle en lecture par rapport au one-liner
est que le premier convertira une ligne vide en un argument vide, tandis que le one-liner ignorera une ligne vide. Dans ce cas, le comportement à une ligne est ce que je préférerais de toute façon, donc double bonus sur le fait qu'il soit compact.
J'utiliserais
IFS=$'\n' cmd $(<file)
mais cela ne fonctionne pas, car$(<file)
est interprété pour former la ligne de commande avant deIFS=$'\n'
prendre effet.Bien que cela ne fonctionne pas dans mon cas, j'ai maintenant appris que de nombreux outils prennent en charge les terminaisons de lignes avec lesquelles, au
null (\000)
lieu denewline (\n)
cela, cela facilite grandement la gestion, par exemple, des noms de fichiers, qui sont des sources courantes de ces situations. :alimente une liste de noms de fichiers pleinement qualifiés en tant qu'arguments à md5 sans globalisation ou interpolation ou quoi que ce soit. Cela conduit à la solution non intégrée
bien que cela, aussi, ignore les lignes vides, bien qu'il capture les lignes qui n'ont que des espaces.
la source
cmd $(<file)
valeurs sans citer (en utilisant la capacité de bash à diviser les mots) est toujours un pari risqué. Si une ligne existe,*
elle sera étendue par le shell à une liste de fichiers.Vous pouvez utiliser le bash intégré
mapfile
pour lire le fichier dans un tableauou, non testé,
xargs
pourrait le fairela source
xargs
n'aide pas non plus.xargs -d
ouxargs -L
-d
option etxargs -L 1
exécute la commande une fois par ligne, mais divise toujours les arguments sur les espaces.mapfile
est très pratique pour moi, car il saisit les lignes vides en tant qu'éléments de tableau, ce que laIFS
méthode ne fait pas.IFS
traite les nouvelles lignes contiguës comme un seul délimiteur ... Merci de la présenter, car je n'étais pas au courant de la commande (bien que, sur la base des données d'entrée de l'OP et de la ligne de commande attendue, il semble qu'il veuille réellement ignorer les lignes vides).Le meilleur moyen que j'ai pu trouver. Fonctionne juste.
la source
IFS
espace, mais la question est de ne pas diviser l'espace?Fichier
La boucle la plus basique (portable) pour diviser un fichier sur des sauts de ligne est:
Qui imprimera:
Oui, avec IFS par défaut = spacetabnewline.
Pourquoi ça marche
IFS
nécessaire.set -f
besoin.-r
option (brute) consiste à éviter la suppression de la plupart des barres obliques inverses.Cela ne fonctionnera pas si le fractionnement et / ou la globalisation sont nécessaires. Dans de tels cas, une structure plus complexe est nécessaire.
Si vous avez besoin (toujours portable) de:
IFS= read -r line
IFS=':' read -r a b c
.Divisez le fichier sur un autre caractère (non portable, fonctionne avec ksh, bash, zsh):
Expansion
Bien sûr, le titre de votre question concerne le fractionnement d'une exécution de commande sur les sauts de ligne en évitant le fractionnement sur les espaces.
La seule façon d'obtenir le fractionnement du shell est de laisser une expansion sans guillemets:
Cela est contrôlé par la valeur de l'IFS et, sur les extensions non cotées, le globbing est également appliqué. Pour effectuer ce travail, vous avez besoin de:
Désactivez l'option shell globbing
set +f
:set + f IFS = '' cmd $ (<fichier)
Bien sûr, cela change la valeur d'IFS et de globbing pour le reste du script.
la source