Nouvelle ligne dans les variables bash

9

J'essaie de stocker plusieurs lignes dans une variable bash, mais cela ne semble pas fonctionner.

Par exemple, si je liste /binun fichier par ligne et que je le stocke $LS, alors je passe en $LStant que stdin à wc, il renvoie toujours 1:

$ ls -1 /bin | wc -l
134
$ LS=$(ls -1 /bin); wc -l <<< $LS
1

Si j'essaie de sortir à l'écran, j'obtiens différents résultats: echoimprime toutes les lignes sur une seule ligne, tandis que printfn'imprime que la première ligne:

#!/bin/bash
LS=$(ls -1 /bin)
echo $LS
printf $LS

Ainsi, une variable bash peut contenir plusieurs lignes?

Wizard79
la source

Réponses:

15

Vous devez guillemet (et vous devriez doubler les variables de citation dans la plupart des cas):

echo "$LS"

Mais n'utilisez pas echo pour imprimer le contenu des variables, utilisez plutôt printf :

printf '%s\n' "$LS"
cuonglm
la source
1
Juste pour mettre un point plus fin sur la réponse globale: printf s'attend à ce qu'une chaîne de format soit son premier paramètre, c'est pourquoi ils n'ont pas vu ce qu'ils attendaient. S'ils veulent voir des retours à la ligne dans la sortie de printf, divisés arbitrairement sur des espaces, le printf "%s\n" $LSferaient.
Jeff Schaller
le% LS devrait être $ LS, btw (je n'ai pas pu faire un montage aussi court)
Jeff Schaller
Merci! Il fonctionne bien sûr aussi avec wcet d'autres commandes:wc -l <<< "$LS"
Wizard79
1
@JeffSchaller: Non, vous ne devriez pas supprimer la variable dans ce cas, utilisez simplement printf '%s\n' "$LS"si vous voulez voir la nouvelle ligne. C'est mal tapé, corrigé!
cuonglm
Pourquoi ne pas utiliser echo pour imprimer des variables?
123
9

Les sauts de ligne sont dans la variable. LS=$(ls -1)définit la variable LSsur la sortie de ls -1(qui produit la même sortie que ls, soit dit en passant, sauf lorsque la sortie est envoyée à un terminal), moins les sauts de ligne de fin.

Le problème est que vous supprimez les sauts de ligne lorsque vous imprimez la valeur. Dans un script shell, $LSne signifie pas «la valeur de la variable LS», cela signifie «prendre la valeur de LS, la diviser en mots selon IFSet interpréter chaque mot comme un modèle global». Pour obtenir la valeur de LS, vous devez écrire "$LS", ou plus généralement mettre $LSentre guillemets doubles.

echo "$LS"imprime la valeur de LS, sauf dans certains shells qui interprètent les caractères antislash, et à l'exception de quelques valeurs commençant par -.

printf "$LS"imprime la valeur LStant qu'il ne contient pas de caractère de pourcentage ou de barre oblique inverse (et avec la plupart des implémentations) ne commence pas par -.

Pour imprimer la valeur de LSexactement, utilisez printf %s "$LS". Si vous voulez une nouvelle ligne à la fin, utilisez printf '%s\n' "$LS".

Notez que ce $(ls)n'est pas, en général, la liste des fichiers dans le répertoire courant. Cela ne fonctionne que lorsque vous avez des noms de fichiers suffisamment apprivoisés. Pour obtenir la liste des noms de fichiers (sauf les fichiers de points), vous devez utiliser un caractère générique: *. Le résultat est une liste de chaînes, pas une chaîne, vous ne pouvez donc pas l'affecter à une variable de chaîne; vous pouvez utiliser une variable de tableau files=(*)dans des shells qui les prennent en charge (ksh93, bash, zsh).

Pour plus d'informations, voir Pourquoi mon script shell s'étouffe-t-il avec les espaces ou d'autres caractères spéciaux?

Gilles 'SO- arrête d'être méchant'
la source
printf "$ LS" succomberait également à l'analyse syntaxique des barres obliques inverses, similaire à echo
cnst
0

Ce qui précède

printf '%s\n' "$LS"

est la seule solution correcte.

Permettez-moi de démontrer que les autres solutions proposées, à la fois avec echoet printf, ne fonctionnent tout simplement pas correctement:

$ mkdir t
$ cd t
$ touch \\n
$ LS=$(ls -l)
$ echo "$LS"
total 0
-rw-r--r--  1 domain  domain  0 Nov  5 06:12

$ printf "$LS\n"
total 0
-rw-r--r--  1 domain  domain  0 Nov  5 06:12

$ printf '%s\n' "$LS"
total 0
-rw-r--r--  1 domain  domain  0 Nov  5 06:12 \n
$ ls -l
total 0
-rw-r--r--  1 domain  domain  0 Nov  5 06:12 \n
$ rm \\n
$ cd ..
$ rmdir t
$

(On peut également tester avec V=$(printf 'line0:\\n\\n\nline1.\n') printf '%s\n' "$V"et variations.)

Alors que \nles noms de fichiers peuvent ne pas être aussi courants, stockez-les git diffdans une variable et vos chances de rencontrer littéralement \naugmentent considérablement.

cnst
la source
Notez que kshet zshégalement soutenir print -r -- "$LS"et a zshégalement echo -E - $LS. csha echo $LS:qcependant que cela ne produit pas le caractère de nouvelle ligne si $ LS est vide, et obtenir une valeur multiligne dans $ LS en premier lieu est assez délicat dans csh.
Stéphane Chazelas