Dans ces cas étonnamment fréquents où ce que vous devez réellement faire est de traiter toutes les lignes non vides à l'intérieur d'une variable d'une certaine manière (y compris en les comptant), vous pouvez définir IFS sur une nouvelle ligne seulement, puis utiliser le mécanisme de séparation des mots du shell pour rompre les lignes non vides à part.
Par exemple, voici une petite fonction shell qui totalise les lignes non vides à l'intérieur de tous les arguments fournis:
lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)
Les parenthèses, plutôt que les accolades, sont utilisées ici pour former la commande composée pour le corps de la fonction. Cela fait que la fonction s'exécute dans un sous-shell afin qu'elle ne pollue pas la variable IFS du monde extérieur et le paramètre d'extension de nom de chemin à chaque appel.
Si vous souhaitez parcourir les lignes non vides, vous pouvez le faire de la même manière:
IFS='
'
set -f
for line in $lines
do
printf '[%s]\n' $line
done
La manipulation d'IFS de cette manière est une technique souvent négligée, également pratique pour effectuer des tâches telles que l'analyse des noms de chemin d'accès qui pourraient contenir des espaces à partir d'une entrée en colonnes délimitée par des tabulations. Cependant, vous devez être conscient que la suppression délibérée du caractère espace généralement inclus dans le paramètre par défaut IFS de space-tab-newline peut finir par désactiver la division des mots aux endroits où vous vous attendez normalement à le voir.
Par exemple, si vous utilisez des variables pour créer une ligne de commande compliquée pour quelque chose comme ffmpeg
, vous souhaiterez peut-être inclure -vf scale=$scale
uniquement lorsque la variable scale
est définie sur quelque chose de non vide. Normalement, vous pouvez y parvenir avec ${scale:+-vf scale=$scale}
mais si IFS n'inclut pas son caractère d'espace habituel au moment où cette expansion de paramètre est effectuée, l'espace entre -vf
et scale=
ne sera pas utilisé comme séparateur de mots et ffmpeg
sera transmis en -vf scale=$scale
tant qu'argument unique, qu'il ne comprendra pas.
Pour remédier à cela, vous avez besoin soit de se assurer IFS a été mis plus normalement avant de faire l' ${scale}
extension, ou faire deux extensions: ${scale:+-vf} ${scale:+scale=$scale}
. Le mot fractionnement que le shell effectue dans le processus d'analyse initiale des lignes de commande, par opposition au fractionnement qu'il fait pendant la phase d'expansion du traitement de ces lignes de commande, ne dépend pas d'IFS.
Quelque chose d'autre qui pourrait valoir la peine si vous voulez faire ce genre de chose serait de créer deux variables shell globales pour contenir juste un onglet et juste une nouvelle ligne:
t=' '
n='
'
De cette façon, vous pouvez simplement inclure $t
et $n
dans les extensions où vous avez besoin d'onglets et de nouvelles lignes, plutôt que de jeter tout votre code avec des espaces entre guillemets. Si vous préférez éviter les espaces entre guillemets dans un shell POSIX qui n'a aucun autre mécanisme pour le faire, vous printf
pouvez vous aider, même si vous avez besoin d'un peu de bidouillage pour contourner la suppression des sauts de ligne dans les extensions de commande:
nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}
Parfois, définir IFS comme s'il s'agissait d'une variable d'environnement par commande fonctionne bien. Par exemple, voici une boucle qui lit un chemin d'accès autorisé à contenir des espaces et un facteur d'échelle à partir de chaque ligne d'un fichier d'entrée délimité par des tabulations:
while IFS=$t read -r path scale
do
ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt
Dans ce cas, le read
module intégré voit IFS défini sur un simple onglet, donc il ne divisera pas la ligne d'entrée qu'il lit également sur les espaces. Mais IFS=$t set -- $lines
cela ne fonctionne pas : le shell se développe au $lines
fur et à mesure qu'il construit les set
arguments du module intégré avant d' exécuter la commande, de sorte que le paramètre temporaire d'IFS d'une manière qui ne s'applique que pendant l'exécution du module intégré lui-même arrive trop tard. C'est pourquoi les extraits de code que j'ai donnés avant tout définissent IFS dans une étape distincte, et pourquoi ils doivent gérer le problème de sa conservation.
wc -l
est exactement équivalent à l'original:<<<$foo
ajoute une nouvelle ligne à la valeur de$foo
(même si elle$foo
était vide). J'explique dans ma réponse pourquoi ce n'était peut-être pas ce que l'on voulait, mais c'est ce qui a été demandé.