Comment puis-je diff la sortie de deux commandes?

165

J'avais imaginé le moyen le plus simple de comparer le contenu de deux répertoires similaires:

diff `ls old` `ls new`

Mais je vois pourquoi cela ne fonctionne pas; diffest en train de recevoir une longue liste de fichiers sur la ligne de commande, plutôt que deux flux comme je l'avais espéré. Comment puis-je passer les deux sorties à diff directement?

Ternaire
la source

Réponses:

246

La substitution de commande substitue `…`le résultat de la commande dans la ligne de commande. diffLa liste des fichiers des deux répertoires est donc considérée comme un argument. Ce que vous voulez, c'est que diffdeux noms de fichier apparaissent sur la ligne de commande et que le contenu de ces fichiers soit la liste des répertoires. C'est ce que la substitution de processus fait.

diff <(ls old) <(ls new)

Les arguments à diffressembler à /dev/fd/3et /dev/fd/4: ce sont des descripteurs de fichier correspondant à deux canaux créés par bash. Quand diffouvrira ces fichiers, il sera connecté au côté lecture de chaque pipe. Le côté écriture de chaque canal est connecté à la lscommande.

Gilles
la source
49
echo <(echo) <(echo)jamais pensé que cela pourrait être si intéressant: D
Aquarius Power 13/12
3
La substitution de processus n'est pas prise en charge par tous les shells , mais les redirections de canaux constituent une solution de contournement simple .
Irfan434
1
Il suffit de mentionner que l' analyse ls n'est pas recommandé unix.stackexchange.com/questions/128985/why-not-parse-ls
Katu
@Katu Le problème lsest qu'il modifie les noms de fichiers. L'analyse de sa sortie est fragile (il ne fonctionne pas avec des noms de fichiers «étranges»). Pour comparer deux listes de répertoires, tout va bien tant que le résultat est sans ambiguïté. Avec des noms de fichier arbitraires, cela nécessiterait une option telle que --quoting-style=escape.
Gilles
1
@will <(…)crée un tuyau. Il semble que le mélange ne fonctionne pas avec les tuyaux, vous ne pouvez donc pas l'utiliser <(…). Dans zsh, vous pouvez remplacer <(…)par =(…)et cela fonctionnera car =(…)place les sorties intermédiaires dans un fichier temporaire. En ce qui concerne bash, je ne pense pas qu'il existe une syntaxe commode, vous devez gérer vous-même les fichiers temporaires.
Gilles
3

Pour zsh, utiliser =(command)crée automatiquement un fichier temporaire et le remplace =(command)par le chemin du fichier lui-même. Avec Substitution de commande, $(command)est remplacé par le résultat de la commande.

Donc, il y a trois options:

  1. Substitution de commande: $(...)
  2. Processus de substitution: <(...)
  3. Substitution de processus à saveur de zsh: =(...)

La substitution n ° 3 des processus aromatisés zsh est très utile et peut être utilisée comme ceci afin de comparer le résultat de deux commandes à l'aide d'un outil diff, par exemple Beyond Compare:

bcomp  =(ulimit -Sa | sort) =(ulimit -Ha | sort)

Pour Au-delà de la comparaison, notez que vous devez utiliser bcomppour ce qui précède (au lieu de bcompare) car bcomplance la comparaison et attend son achèvement. Si vous utilisez bcompare, cela lance la comparaison et se termine immédiatement en raison de la disparition des fichiers temporaires créés pour stocker la sortie des commandes.

Lisez plus ici: http://zsh.sourceforge.net/Intro/intro_7.html

Notez aussi ceci:

Notez que le shell crée un fichier temporaire et le supprime une fois la commande terminée.

et ce qui suit est la différence entre les deux types de substitution de processus pris en charge par zsh (c'est-à-dire n ° 2 et n ° 3):

Si vous lisez la page de manuel de zsh, vous remarquerez peut-être que <(...) est une autre forme de substitution de processus similaire à = (...). Il y a une différence importante entre les deux. Dans le cas <(...), le shell crée un tube nommé (FIFO) à la place d'un fichier. C’est mieux, car cela ne remplit pas le système de fichiers; mais cela ne fonctionne pas dans tous les cas. En fait, si nous avions remplacé = (...) par <(...) dans les exemples ci-dessus, ils auraient tous cessé de fonctionner à l'exception de fgrep -f <(...). Vous ne pouvez pas éditer un tuyau ou l'ouvrir en tant que dossier de courrier; fgrep n'a cependant aucun problème à lire une liste de mots à partir d'un tuyau. Vous vous demandez peut-être pourquoi diff <(foo) bar ne fonctionne pas, puisque foo | travaux de barre de diff; C'est parce que diff crée un fichier temporaire s'il remarque que l'un de ses arguments est -, puis copie son entrée standard dans le fichier temporaire.

Référence: https://unix.stackexchange.com/questions/393349/difference-between-subshells-and-process-substitution

Ashutosh Jindal
la source
2
$(...)n'est pas une substitution de processus, c'est une substitution de commande . <(...)est la substitution de processus. C'est pourquoi le passage cité ne mentionne pas $(...)du tout.
Muru
2

Coquille de poisson

Dans Fish shell, vous devez canaliser psub . Voici un exemple de comparaison des configurations heroku et dokku avec Beyond Compare :

bcompare (ssh [email protected] dokku config myapp | sort | psub) (heroku config -a myapp | sort | psub)
WooYek
la source
1
Un autre outil de diff graphique meldest open source et disponible dans les référentiels Ubuntu et EPEL. meldmerge.org
phiphi
0

J'utilise souvent la technique décrite dans la réponse acceptée:

diff <(ls old) <(ls new)

mais je trouve que je l'utilise habituellement avec des commandes beaucoup plus complexes que l'exemple ci-dessus. Dans de tels cas, il peut être ennuyeux d’exécuter la commande diff. J'ai trouvé des solutions que d'autres pourraient trouver utiles.

Je trouve que 99% du temps, j'essaie les commandes appropriées avant d'exécuter diff. Par conséquent, les commandes que je veux différencier sont là dans mon histoire ... pourquoi ne pas les utiliser?

Je me sers de la commande Fix (fc) bash intégrée pour exécuter les deux dernières commandes:

$ echo A
A
$ echo B
B
$ diff --color <( $(fc -ln -1 -1) ) <( $(fc -ln -2 -2 ) )
1c1
< B
---
> A

Les drapeaux fc sont:

-n : Pas de numéro. Il supprime les numéros de commande lors de la liste.

-l : Listing: Les commandes sont listées sur la sortie standard.

Les -1 -1références aux positions de début et de fin dans l'historique, dans ce cas, de la dernière commande à la dernière, qui ne produit que la dernière.

Enfin, nous encapsulons ceci $()pour exécuter la commande dans un sous-shell.

De toute évidence, il est un peu difficile à taper afin que nous puissions créer un alias:

alias dl='diff --color <( $(fc -ln -1 -1) ) <( $(fc -ln -2 -2 ) )'

Ou nous pouvons créer une fonction:

dl() {
    if [[ -z "$1" ]]; then
        first="1"
    else
        first="$1"
    fi
    if [[ -z "$2" ]]; then
        last="2"
    else
        last="$2"
    fi
    # shellcheck disable=SC2091
    diff --color <( $(fc -ln "-$first" "-$first") ) <( $(fc -ln "-$last" "-$last") )
}

qui prend en charge la spécification des lignes d’historique à utiliser. Après avoir utilisé les deux, je trouve que l'alias est la version que je préfère.

htaccess
la source