Come diff diffondo l'output di due comandi?


165

Avevo immaginato che il modo più semplice per confrontare i contenuti di due directory simili sarebbe qualcosa di simile

diff `ls old` `ls new`

Ma vedo perché questo non funziona; diffviene distribuito un lungo elenco di file sulla riga di comando, anziché due flussi come avevo sperato. Come faccio a passare le due uscite a diff direttamente?


Risposte:


245

La sostituzione dei comandi sostituisce `…`l'output del comando nella riga di comando, quindi diffvede l'elenco dei file in entrambe le directory come argomenti. Quello che vuoi è diffvedere due nomi di file sulla sua riga di comando e avere il contenuto di questi file come elenchi di directory. Questo è ciò che fa la sostituzione del processo .

diff <(ls old) <(ls new)

Gli argomenti diffsaranno simili /dev/fd/3e /dev/fd/4: sono descrittori di file corrispondenti a due pipe create da bash. Quando diffapre questi file, verrà collegato al lato di lettura di ogni pipe. Il lato di scrittura di ogni pipe è collegato al lscomando.


48
echo <(echo) <(echo)non avrei mai pensato che potesse essere così interessante: D
Aquarius Power il

3
La sostituzione del processo non è supportata da tutte le shell , ma i reindirizzamenti delle pipe sono una soluzione ordinata .
Irfan434,

1
Solo per dire che l'analisi di ls non è raccomandata unix.stackexchange.com/questions/128985/why-not-parse-ls
Katu

@Katu Il problema lsè che altera i nomi dei file. L'analisi del suo output è fragile (non funziona con nomi di file "strani"). Per confrontare due elenchi di directory, va bene purché l'output sia inequivocabile. Con nomi di file arbitrari, ciò richiederebbe un'opzione come --quoting-style=escape.
Gilles,

1
@will <(…)crea una pipe. Sembra che la fusione non funzioni con i tubi, quindi non puoi usarli <(…). In zsh, puoi sostituirlo <(…)con =(…)e funzionerà perché =(…)inserisce gli output intermedi in un file temporaneo. In bash non penso che ci sia una sintassi conveniente, dovresti gestire tu stesso i file temporanei.
Gilles,

3

Per zsh, l'utilizzo =(command)crea automaticamente un file temporaneo e lo sostituisce =(command)con il percorso del file stesso. Con Sostituzione comando, $(command)viene sostituito con l' output del comando.

Quindi ci sono tre opzioni:

  1. Sostituzione comando: $(...)
  2. Sostituzione del processo: <(...)
  3. Sostituzione del processo aromatizzato zsh: =(...)

La sottosezione di processo aromatizzata zsh, n. 3, è molto utile e può essere utilizzata in questo modo per confrontare l'output di due comandi utilizzando uno strumento diff, ad esempio Beyond Compare:

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

Per Beyond Compare, tenere presente che è necessario utilizzare bcompquanto sopra (anziché bcompare) poiché bcompavvia il confronto e attende il completamento. Se lo usi bcompare, questo avvia il confronto e termina immediatamente a causa della quale scompaiono i file temporanei creati per memorizzare l'output dei comandi.

Maggiori informazioni qui: http://zsh.sourceforge.net/Intro/intro_7.html

Notare anche questo:

Si noti che la shell crea un file temporaneo e lo elimina al termine del comando.

e la seguente che è la differenza tra i due tipi di sostituzione del processo supportati da zsh (cioè # 2 e # 3):

Se leggi la pagina man di zsh, potresti notare che <(...) è un'altra forma di sostituzione del processo che è simile a = (...). C'è una differenza importante tra i due. Nel caso <(...), la shell crea una pipe denominata (FIFO) anziché un file. Questo è meglio, dal momento che non riempie il file system; ma non funziona in tutti i casi. Infatti, se avessimo sostituito = (...) con <(...) negli esempi sopra, tutti avrebbero smesso di funzionare tranne fgrep -f <(...). Non è possibile modificare una pipe o aprirla come una cartella di posta; fgrep, tuttavia, non ha problemi con la lettura di un elenco di parole da una pipe. Potresti chiederti perché la barra diff <(foo) non funziona, poiché foo | diff - bar funziona; questo perché diff crea un file temporaneo se nota che uno dei suoi argomenti è -, quindi copia l'input standard nel file temporaneo.

Riferimento: https://unix.stackexchange.com/questions/393349/difference-between-subshells-and-process-substitution


2
$(...)non è una sostituzione di processo, è una sostituzione di comando . <(...)è la sostituzione del processo. Ecco perché il passaggio citato non menziona $(...)affatto.
muru,

2

Conchiglia di pesce

In Fish shell devi convogliare in psub . Ecco un esempio del confronto di configurazione heroku e dokku con Beyond Compare :

bcompare (ssh me@myapp.pl dokku config myapp | sort | psub) (heroku config -a myapp | sort | psub)

1
Un altro strumento grafico diff meldè open source e disponibile nei repository Ubuntu ed EPEL. meldmerge.org
PhiPhi

0

Uso spesso la tecnica descritta nella risposta accettata:

diff <(ls old) <(ls new)

ma trovo che di solito lo uso con comandi molto più complessi dell'esempio sopra. In questi casi può essere fastidioso creare il comando diff. Ho escogitato alcune soluzioni che altri potrebbero trovare utili.

Trovo che il 99% delle volte provo i comandi pertinenti prima di eseguire diff. Di conseguenza i comandi che desidero diff sono proprio lì nella mia storia ... perché non usarli?

Faccio uso del comando di comando bash Fix Fix (fc) per eseguire gli ultimi due comandi:

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

I flag fc sono:

-n : nessun numero. Sopprime i numeri di comando durante l'elenco.

-l : Elenco: i comandi sono elencati nell'output standard.

il -1 -1riferimento all'inizio e alla fine nella storia, in questo caso è dall'ultimo comando all'ultimo comando che produce solo l'ultimo comando.

Infine, lo includiamo $()per eseguire il comando in una subshell.

Ovviamente questo è un po 'una seccatura da digitare, quindi possiamo creare un alias:

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

Oppure possiamo creare una funzione:

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") )
}

che supporta la specifica delle righe della cronologia da utilizzare. Dopo aver usato entrambi trovo che l'alias sia la versione che preferisco.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.