Faire écho à une commande de queue produit une sortie inattendue?

8

Cette commande, lorsqu'elle est exécutée seule, produit le résultat attendu (la dernière ligne de la crontab):

tail -n 1 /etc/crontab

Cependant, lorsque je l'exécute dans le cadre d'une commande echo pour envoyer le résultat à un fichier, il ajoute un résumé de tous les fichiers du répertoire de travail, plus le résultat attendu:

sudo bash -c 'echo $(tail -n 1 /etc/crontab) > /path/to/file'

Pourquoi cette commande a-t-elle produit les données supplémentaires?

Davidw
la source
3
Que echofais-tu ici? Pensez aussitail -n 1 /etc/crontab | sudo tee /path/to/file >/dev/null
ctrl-alt-delor

Réponses:

22

Votre ligne crontab contient un ou plusieurs astérisques *indiquant "à tout moment". Lorsque cette ligne est substituée à partir de la substitution de commande, le résultat est quelque chose comme

echo * * * * * cmd > /path/to/file

Bien que la plupart des extensions supplémentaires ne soient pas appliquées à la sortie de la substitution de commandes, l' expansion du nom de chemin est (tout comme la division des champs) :

Les résultats de la substitution de commande ne doivent pas être traités pour une expansion supplémentaire du tilde, une expansion de paramètre, une substitution de commande ou une expansion arithmétique. Si une substitution de commande se produit à l'intérieur de guillemets doubles, le fractionnement de champ et l' expansion de nom de chemin ne doivent pas être effectués sur les résultats de la substitution.

L'expansion du nom de chemin est ce qui se transforme *.txten une liste de noms de fichiers correspondants (globbing), où *correspond tout. Le résultat final est que vous obtenez chaque nom de fichier (non masqué) dans le répertoire de travail répertorié pour chacun *dans votre ligne crontab.


Vous pouvez résoudre ce problème en citant l'extension, si le code que vous avez publié était représentatif d'une commande plus complexe:

sudo bash -c 'echo "$(tail -n 1 /etc/crontab)" > /path/to/file'

mais plus simplement, il suffit de perdre echoentièrement:

sudo bash -c 'tail -n 1 /etc/crontab > /path/to/file'

Cela devrait faire ce que vous voulez et c'est aussi plus simple (la seule autre différence matérielle est que cette version omettra le fractionnement de champ qui aurait autrement eu lieu, donc les séries d'espaces ne seront pas réduites).

Michael Homer
la source
5
Comme / etc / crontab est presque toujours lisible par tout le monde, toute la supercherie "bash -c" est en fait inutile: tail -n -1 /etc/crontab | sudo tee /path/to/filec'est l'idiome que j'ai trouvé le moins sujet aux erreurs lors de la redirection de la sortie vers des fichiers qui nécessitent des privilèges de superutilisateur.
Basse
5

Prenons un répertoire avec ces fichiers:

$ ls
crontab  file1  file2  file3
$ cat crontab
f*

Maintenant, exécutons la commande tail:

$ tail -n 1 crontab
f*

Ce qui précède est la dernière ligne de crontabet c'est ce que nous attendons. Toutefois:

$ echo $(tail -n 1 crontab)
file1 file2 file3

Les guillemets doubles éliminent ce problème:

$ echo "$(tail -n 1 crontab)"
f*

Sans les guillemets doubles, le résultat de la substitution de commande est développé par le shell. L'une des extensions est l'extension du nom de chemin . Dans le cas ci-dessus, cela signifie qu'il f*est développé pour correspondre à chaque nom de fichier commençant par f.

À moins que vous ne vouliez explicitement des extensions de shell, mettez toutes vos variables shell et / ou substitutions de commandes entre guillemets.

John1024
la source
4

le mécanisme de globing shell sera étendu *au fichier local.

crontab line est susceptible d'avoir un *espace réservé pour tout.

Par exemple, cette ligne dans crontab fonctionne à 7h47 le dimanche, la première étoile signifie n'importe quel jour, la deuxième n'importe quel mois.

47  7 * * 0 /run/on/sunday

alors vous tail, et émettez

echo 47  7 * * 0 /run/on/sunday

qui se développera *dans un fichier local.

Archemar
la source