Différence entre 'ls' et 'echo $ (ls)'

27

Considérez les deux échantillons de coque

$ ls
myDoc.html
SomeDirectory
someDoc.txt

et

$ echo $(ls)
myDoc.html SomeDirectory someDoc.txt

Le premier s'exécute lsqui, si je comprends bien, ajoute le contenu du répertoire de travail actuel au stdoutfichier (qui est ce que le terminal affiche). Est-ce correct?

Le second prend la valeur de la lscommande (c'est-à-dire le contenu du répertoire de travail courant) et l'imprime dans le stdoutfichier. Est-ce correct?

Pourquoi les deux commandes donnent-elles des sorties différentes?

Owen
la source
1
parce que tu fais l'écho. La sortie de ls comme entrée pour la commande echo.
ankidaemon
Il s'agit d'un comportement "attendu", que quelqu'un expliquera sous peu. Cependant, êtes-vous sûr qu'en lssoi, il ne vous donne pas plusieurs éléments par ligne?
roaima
@roaima columnized ls est un vestige d'une "fonction" bsd. les versions unix impriment une entrée par ligne
Steve Cox
@SteveCox Je n'ai pas utilisé UNIX depuis le début de SVR4, donc ma mémoire est un peu floue. Merci pour le rappel.
roaima
Pourquoi ne pas echo *?
jdh8

Réponses:

70

Lorsque vous exécutez cette commande:

ls

le terminal affiche la sortie de ls.

Lorsque vous exécutez cette commande:

echo $(ls)

le shell capture la sortie de $(ls)et effectue la division des mots dessus. Avec la valeur par défaut IFS, cela signifie que toutes les séquences d'espaces blancs, y compris les caractères de nouvelle ligne, sont remplacées par un seul blanc. C'est pourquoi la sortie de echo $(ls)apparaît sur une seule ligne.

Pour une discussion avancée sur le fractionnement de mots, consultez la FAQ de Greg .

Suppression de la division des mots

Le shell n'effectue pas de fractionnement de mots sur les chaînes entre guillemets. Ainsi, vous pouvez supprimer le fractionnement de mots et conserver la sortie multiligne avec:

echo "$(ls)"

ls et sortie multiligne

Vous avez peut-être remarqué que lsparfois imprime plus d'un fichier par ligne:

$ ls
file1  file2  file3  file4  file5  file6

Il s'agit de la valeur par défaut lorsque la sortie de lsva à un terminal. Lorsque la sortie ne va pas directement à un terminal, lschange sa valeur par défaut en un fichier par ligne:

$ echo "$(ls)"
file1
file2
file3
file4
file5
file6

Ce comportement est documenté dans man ls.

Autre subtilité: la substitution de commandes et les sauts de ligne

$(...)est la substitution de commande et le shell supprime les caractères de fin de ligne de sortie de la substitution de commande . Cela n'est normalement pas perceptible car, par défaut, echoajoute une nouvelle ligne à la fin de sa sortie. Donc, si vous perdez une nouvelle ligne à la fin de $(...)et que vous en gagnez une echo, il n'y a pas de changement. Si, cependant, la sortie de votre commande se termine par 2 ou plusieurs caractères de echoretour à la ligne alors qu’elle n’en ajoute qu’un, il vous manquera un ou plusieurs retours à la ligne. Par exemple, nous pouvons utiliser printfpour générer des caractères de nouvelle ligne de fin. Notez que les deux commandes suivantes, malgré le nombre différent de sauts de ligne, produisent la même sortie d'une ligne vierge:

$ echo "$(printf "\n")"

$ echo "$(printf "\n\n\n\n\n")"

$ 

Ce comportement est documenté dans man bash.

Autre surprise: extension du nom de chemin, deux fois

Créons trois fichiers:

$ touch 'file?' file1 file2

Observez la différence entre ls file?et echo $(ls file?):

$ ls file?
file?  file1  file2
$ echo $(ls file?)
file? file1 file2 file1 file2

Dans le cas de echo $(ls file?), le glob de fichier file?est développé deux fois , provoquant les noms de fichier file1et file2apparaissant deux fois dans la sortie. En effet, comme le souligne Jeffiekins, l' expansion du nom de chemin est effectuée d'abord par le shell avant l' lsexécution, puis à nouveau avant l' echoexécution.

La deuxième extension de chemin peut être supprimée si nous utilisons des guillemets doubles:

$ echo "$(ls file?)"
file?
file1
file2
John1024
la source
4
de plus, en utilisant isattyvous pouvez vérifier si stdout est un tty, ls l'utilise pour déterminer s'il faut utiliser des couleurs ou non.
Janus Troelsen
1
Les citations ne correspondent-elles pas dans le dernier extrait de code? Ou $( )fournissez- vous réellement une barrière de syntaxe pour que vous n'ayez pas besoin d'interpoler?
chat
6
@cat $()fournit une barrière de syntaxe.
Random832
2
Une autre différence importante: si un nom de fichier contient un caractère générique, la echocommande étendra le caractère générique . Par exemple, si lsrenvoie un fichier? file1 file2 , puis echo $(ls)retournera le fichier? fichier1 fichier2 fichier1 fichier2 .
Jeffiekins
1
@Jeffiekins echone développe pas les caractères génériques; le shell fait avant de passer l'expansion résultante comme arguments à echo.
chepner
0

ls sait s'il envoie ou non une sortie à un terminal.

L'exécution lsà l'invite de commandes écrira sur votre pseudo-tty. Rediriger la sortie de lsne se fera généralement pas vers un pseudo-tty et lsformatera la sortie différemment.

mpez0
la source