Diferença de cordas em Bash

110

Estou tentando encontrar uma maneira de determinar a diferença entre duas strings em meu script. Eu poderia fazer isso facilmente com diff ou comm, mas não estou lidando com arquivos e prefiro não gerá-los em arquivos, fazer a comparação e ler de volta.

Vejo que comm, diff, cmp permitem a passagem de dois arquivos OU um arquivo e entrada padrão - acho que é bom se eu não quero dois arquivos de saída ... mas ainda assim é um bocado chato.

Andei vasculhando pensando que posso usar grep ou expressões regulares - mas acho que não.

codeforester
fonte
1
o que você realmente quer fazer?
Você pode usar manipulações de substring e operações de teste integradas com as alterações do IFS para comparar, mas você precisa saber se deseja comparar caractere por caractere, palavra por palavra, linha por linha, ignorar espaço em branco ...
technosaurus

Respostas:

198

Usando diffou comou o que você quiser:

diff  <(echo "$string1" ) <(echo "$string2")

FAQ do Greg's Bash: Substituição de Processo

ou com um tubo nomeado

mkfifo ./p
diff - p <<< "$string1" & echo "$string2" > p

Perguntas frequentes do Greg's Bash: Trabalhando com Pipes Nomeados

O pipe nomeado também é conhecido como FIFO.

O -próprio é para entrada padrão.

<<< é uma "string aqui".

&é parecido, ;mas o coloca em segundo plano

Ian Kelling
fonte
5
1 para resposta correta. 1 para uma grande explicação dos símbolos. Além disso, o FAQ do Greg Bash foi movido para: mywiki.wooledge.org Os links para as páginas acima estão agora em mywiki.wooledge.org/ProcessSubstitution e mywiki.wooledge.org/BashFAQ/085
timemachine3030
THX! e também, isso mostrará os descritores de arquivo dinâmicoFUNC(){ echo "$@"; "$@"; }; FUNC diff <(echo a) <(echo b);
Aquarius Power
Eu estava procurando por isso para comparar dois shasums. Não tenho certeza se existe uma maneira mais elegante de fazer isso, mas funciona.
fuma em
Isso parece funcionar se houver várias linhas em $ string1 e $ string2, e diff gera as linhas que foram adicionadas ou subtraídas. E se a string for uma única linha e linha e houver alguma diferença entre as duas strings?
alpha_989
@ alpha_989, aqui está sua resposta: $ diff <(echo "Here are the letters in String One.") <(echo "Here are the characters in String Two.") \n 1c1 \n < Here are the letters in String One. \n --- \n > Here are the characters in String Two. \nUsar o pipe é semelhante, exceto que mostra um número de processo, começa com o 1c1depois do próximo $e espera até que você pressione <kbd> Enter <kbd> (ou você pode fazer outros comandos ...)
bballdave025
19

Isso me lembra desta questão: Como você pode diferenciar dois pipelines no Bash?

Se você estiver em uma sessão bash, poderá fazer:

diff <cmd1 <cmd2
diff <(foo | bar) <(baz | quux)

com a <criação de pipes nomeados anônimos - gerenciados pelo bash - para que sejam criados e destruídos automaticamente, ao contrário dos arquivos temporários.

Portanto, se você conseguir isolar suas duas strings diferentes como parte de um comando (grep, awk, sed, ...), você pode fazer - por exemplo - algo como:

diff < grep string1 myFile < grep string2 myFile

(se você acha que tem em seu arquivo linhas como string1=very_complicated_valuee a string2=another_long_and_complicated_value': sem saber o formato interno de seu arquivo, não posso recomendar um comando preciso)

VonC
fonte
13

Eu prefiro um cmprecurso de Substituição de Processo do bash:

$ cmp -bl <(echo -n abcda) <(echo -n aqcde)
  2 142 b    161 q
  5 141 a    145 e

Dizendo na posição 2, ab ocorre para o primeiro, mas aq para o segundo. Na posição 5, outra diferença está acontecendo. Basta substituir essas strings por variáveis ​​e pronto.

Johannes Schaub - litb
fonte
Isso funciona apenas quando as cordas são do mesmo comprimento!
strpeter
11

Digamos que você tenha três cordas

a="this is a line"
b="this is"
c="a line"

Para remover o prefixo b de um

echo ${a#"$b"}  # a line

Para remover o sufixo c de um

echo ${a%"$c"}  # this is
Pithikos
fonte
2
Eu acho que essa é a maneira bash de fazer isso. Funcionou bem. Essa sintaxe é um pouco difícil de entender.
Mikael Roos
@MikaelRoos Concordo. Mais fácil de ler (pelo menos para mim) seria usar sed: echo "$a" | sed "s!^$b!!g" (eu troquei o separador sed padrão / for! No caso das variáveis ​​sendo tratadas serem caminhos. Além disso, você poderia usar uma string here em vez de echo:. sed ... <<< $a)
ACK_stoverflow
1

Outro exemplo:

before="184613 102050 83756 63054"
after="184613 102050 84192 83756 63054"

comm -23 <(tr ' ' $'\n' <<< $after | sort) <(tr ' ' $'\n' <<< $before | sort)

Saídas

84192

Resposta original aqui

Sida Zhou
fonte