Como diferencio a saída de dois comandos?

165

Eu imaginava que a maneira mais simples de comparar o conteúdo de dois diretórios semelhantes seria algo como

diff `ls old` `ls new`

Mas vejo por que isso não funciona; diffestá sendo entregue uma grande e longa lista de arquivos na linha de comando, em vez de dois fluxos como eu esperava. Como passo as duas saídas diretamente para o diff?

Ternário
fonte

Respostas:

246

A substituição de comando substitui `…`a saída do comando na linha de comando; portanto, diffa lista de arquivos nos dois diretórios é apresentada como argumento. O que você deseja é diffver dois nomes de arquivos em sua linha de comandos e ter o conteúdo desses arquivos nas listagens de diretório. É isso que a substituição de processo faz.

diff <(ls old) <(ls new)

Os argumentos para diffterão a aparência /dev/fd/3e /dev/fd/4: são descritores de arquivo correspondentes a dois pipes criados pelo bash. Quando diffabrir esses arquivos, ele será conectado ao lado de leitura de cada canal. O lado de gravação de cada canal está conectado ao lscomando.

Gilles
fonte
49
echo <(echo) <(echo)nunca pensei que isso poderia ser tão interessante: D
Aquarius Power
3
A substituição do processo não é suportada por todos os shells , mas os redirecionamentos de canal são uma solução interessante .
precisa saber é o seguinte
11
Só para mencionar que a análise ls não é recomendado unix.stackexchange.com/questions/128985/why-not-parse-ls
Katu
@ Katu O problema lsé que ele gerencia nomes de arquivos. A análise de sua saída é frágil (não funciona com nomes de arquivos "estranhos"). Para comparar duas listagens de diretórios, não há problema, desde que a saída seja inequívoca. Com nomes de arquivos arbitrários, isso exigiria uma opção como --quoting-style=escape.
Gilles
11
@will <(…)cria um tubo. Parece que o meld não funciona com tubos, então você não pode usá-lo <(…). No zsh, você pode substituir <(…)por =(…)e funcionará porque =(…)coloca as saídas intermediárias em um arquivo temporário. No bash, acho que não há nenhuma sintaxe conveniente, você mesmo precisará gerenciar os arquivos temporários.
Gilles
3

Para zsh, o uso =(command)cria automaticamente um arquivo temporário e substitui =(command)o caminho do próprio arquivo. Com Substituição de Comando, $(command)é substituído pela saída do comando.

Portanto, existem três opções:

  1. Substituição de Comando: $(...)
  2. Substituição de processo: <(...)
  3. Substituição de processo com sabor zsh: =(...)

A subscrição do processo com sabor zsh, # 3, é muito útil e pode ser usada dessa maneira para comparar a saída de dois comandos usando uma ferramenta diff, por exemplo, Beyond Compare:

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

Para Além da comparação, observe que você deve usar bcompo acima (em vez de bcompare), pois bcompinicia a comparação e aguarda a conclusão. Se você usar bcompare, isso inicia a comparação e sai imediatamente, devido ao qual os arquivos temporários criados para armazenar a saída dos comandos desaparecem.

Leia mais aqui: http://zsh.sourceforge.net/Intro/intro_7.html

Observe também o seguinte:

Observe que o shell cria um arquivo temporário e o exclui quando o comando é concluído.

e o seguinte, que é a diferença entre os dois tipos de substituição de processo suportados pelo zsh (ou seja, nº 2 e nº 3):

Se você ler a página de manual do zsh, poderá notar que <(...) é outra forma de substituição de processo semelhante a = (...). Há uma diferença importante entre os dois. No caso <(...), o shell cria um pipe nomeado (FIFO) em vez de um arquivo. Isso é melhor, pois não preenche o sistema de arquivos; mas não funciona em todos os casos. De fato, se substituíssemos = (...) por <(...) nos exemplos acima, todos eles teriam parado de funcionar, exceto fgrep -f <(...). Você não pode editar um canal ou abri-lo como uma pasta de correio; O fgrep, no entanto, não tem problemas em ler uma lista de palavras de um pipe. Você pode se perguntar por que a barra diff <(foo) não funciona, pois foo | obras de barras; isso ocorre porque o diff cria um arquivo temporário se perceber que um de seus argumentos é - e copia sua entrada padrão no arquivo temporário.

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

Ashutosh Jindal
fonte
2
$(...)não é substituição de processo, é substituição de comando . <(...)é substituição de processo. É por isso que a passagem citada não menciona $(...)nada.
Muru
2

Casca de peixe

Na casca do peixe, você precisa entrar no psub . Aqui está um exemplo da comparação de configurações heroku e dokku com o Beyond Compare :

bcompare (ssh [email protected] dokku config myapp | sort | psub) (heroku config -a myapp | sort | psub)
WooYek
fonte
11
Outra ferramenta gráfica diff meldé a de código aberto e disponível nos repositórios Ubuntu e EPEL. meldmerge.org
phiphi
0

Costumo usar a técnica descrita na resposta aceita:

diff <(ls old) <(ls new)

mas acho que geralmente o uso com comandos muito mais complexos que o exemplo acima. Nesses casos, pode ser irritante criar o comando diff. Eu vim com algumas soluções que outros podem achar úteis.

Acho que 99% das vezes eu tento os comandos relevantes antes de executar o diff. Consequentemente, os comandos que eu quero diferenciar estão aí na minha história ... por que não usá-los?

Utilizo o bash Fix Command (fc) interno para executar os dois últimos comandos:

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

Os sinalizadores fc são:

-n : sem número. Suprime os números de comando ao listar.

-l : Listagem: Os comandos estão listados na saída padrão.

o -1 -1referem-se às posições inicial e final da história, neste caso, do último comando ao último comando que produz apenas o último comando.

Por fim, envolvemos isso $()para executar o comando em um subshell.

Obviamente, isso é um pouco trabalhoso de digitar, para que possamos criar um alias:

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

Ou podemos criar uma função:

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 suporta a especificação das linhas do histórico a serem usadas. Depois de usar os dois, acho que o alias é a versão que eu prefiro.

htaccess
fonte