¿Cómo difiero la salida de dos comandos?

165

Había imaginado que la forma más sencilla de comparar el contenido de dos directorios similares sería algo así como

diff `ls old` `ls new`

Pero veo por qué esto no funciona; diffse le está entregando una larga lista de archivos en la línea de comando, en lugar de dos secuencias como esperaba. ¿Cómo paso las dos salidas a diff directamente?

Ternario
fuente

Respuestas:

246

La sustitución de comandos sustituye `…`la salida del comando en la línea de comandos, por lo que diffve la lista de archivos en ambos directorios como argumentos. Lo que desea es diffver dos nombres de archivo en su línea de comando y que el contenido de estos archivos sea el listado de directorios. Eso es lo que hace la sustitución del proceso .

diff <(ls old) <(ls new)

Los argumentos a diffse verán /dev/fd/3y /dev/fd/4: son descriptores de archivo correspondientes a dos tuberías creadas por bash. Cuando diffabra estos archivos, se conectará al lado de lectura de cada canalización. El lado de escritura de cada tubería está conectado al lscomando.

Gilles
fuente
49
echo <(echo) <(echo)Nunca pensé que esto podría ser tan interesante: D
Acuario Power
3
La sustitución de procesos no es compatible con todos los shells , pero las redirecciones de tuberías son una solución clara .
Irfan434
1
Solo para mencionar que no se recomienda analizar ls unix.stackexchange.com/questions/128985/why-not-parse-ls
Katu
@Katu El problema lses que altera los nombres de los archivos. Analizar su salida es frágil (no funciona con nombres de archivo "extraños"). Para comparar dos listados de directorio, está bien siempre que la salida no sea ambigua. Con nombres de archivo arbitrarios, esto requeriría una opción como --quoting-style=escape.
Gilles
1
@will <(…)crea una tubería. Parece que la fusión no funciona con tuberías, por lo que no puede usarla <(…). En zsh, puede reemplazar <(…)por =(…)y funcionará porque =(…)coloca las salidas intermedias en un archivo temporal. En bash, no creo que haya una sintaxis conveniente, tendría que administrar los archivos temporales usted mismo.
Gilles
3

Para zsh, el uso =(command)crea automáticamente un archivo temporal y lo reemplaza =(command)con la ruta del archivo en sí. Con la sustitución de comandos, $(command)se reemplaza con la salida del comando.

Entonces hay tres opciones:

  1. Sustitución de comando: $(...)
  2. Proceso de sustitución: <(...)
  3. Sustitución del proceso con sabor a zsh: =(...)

La sustitución del proceso con sabor zsh, # 3, es muy útil y se puede usar así para comparar la salida de dos comandos usando una herramienta diff, por ejemplo, Beyond Compare:

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

Para Beyond Compare, tenga en cuenta que debe usar bcomplo anterior (en lugar de bcompare) desde que bcompinicia la comparación y espera a que se complete. Si lo usa bcompare, se inicia la comparación y se cierra inmediatamente debido a que los archivos temporales creados para almacenar la salida de los comandos desaparecen.

Lea más aquí: http://zsh.sourceforge.net/Intro/intro_7.html

También tenga en cuenta esto:

Tenga en cuenta que el shell crea un archivo temporal y lo elimina cuando finaliza el comando.

y la siguiente, que es la diferencia entre los dos tipos de sustitución de Procesos soportados por zsh (es decir, # 2 y # 3):

Si lee la página de manual de zsh, puede notar que <(...) es otra forma de sustitución de proceso que es similar a = (...). Hay una diferencia importante entre los dos. En el caso <(...), el shell crea una tubería con nombre (FIFO) en lugar de un archivo. Esto es mejor, ya que no llena el sistema de archivos; pero no funciona en todos los casos. De hecho, si hubiéramos reemplazado = (...) con <(...) en los ejemplos anteriores, todos habrían dejado de funcionar, excepto fgrep -f <(...). No puede editar una tubería o abrirla como una carpeta de correo; Sin embargo, fgrep no tiene problemas para leer una lista de palabras de una tubería. Quizás se pregunte por qué la barra diff <(foo) no funciona, ya que foo | trabajos de barra diferencial; Esto se debe a que diff crea un archivo temporal si nota que uno de sus argumentos es -, y luego copia su entrada estándar al archivo temporal.

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

Ashutosh Jindal
fuente
2
$(...)no es sustitución de proceso, es sustitución de comando . <(...)es la sustitución del proceso Es por eso que el pasaje citado no menciona $(...)en absoluto.
muru
2

Concha de pescado

En Fish Shell tienes que conectarlo a psub . Aquí hay un ejemplo de comparación de configuración heroku y dokku con Beyond Compare :

bcompare (ssh [email protected] dokku config myapp | sort | psub) (heroku config -a myapp | sort | psub)
WooYek
fuente
1
Otra herramienta gráfica de diferencia es meldla de código abierto y está disponible en los repositorios de Ubuntu y EPEL. meldmerge.org
phiphi
0

A menudo uso la técnica descrita en la respuesta aceptada:

diff <(ls old) <(ls new)

pero encuentro que generalmente lo uso con comandos mucho más complejos que el ejemplo anterior. En tales casos, puede ser molesto crear el comando diff. He encontrado algunas soluciones que otros pueden encontrar útiles.

Encuentro que el 99% del tiempo pruebo los comandos relevantes antes de ejecutar diff. En consecuencia, los comandos que quiero diferenciar están ahí en mi historia ... ¿por qué no usarlos?

Hago uso del comando Fix (fc) bash incorporado para ejecutar los últimos dos comandos:

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

Las banderas fc son:

-n : Sin número. Suprime los números de comando al enumerar.

-l : Listado: Los comandos se enumeran en la salida estándar.

se -1 -1refieren a la posición de inicio y fin en el historial, en este caso es desde el último comando hasta el último comando que produce solo el último comando.

Por último, envolvemos esto $()para ejecutar el comando en una subshell.

Obviamente, esto es un poco difícil de escribir para que podamos crear un alias:

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

O podemos crear una función:

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

que admite especificar las líneas del historial que se utilizarán. Después de usar ambos, encuentro que el alias es la versión que prefiero.

htaccess
fuente