Różnica strun w Bash


110

Próbuję znaleźć sposób na określenie różnicy między dwoma ciągami znaków w moim skrypcie. Mógłbym to łatwo zrobić za pomocą diff lub comm, ale nie mam do czynienia z plikami i wolałbym nie wyprowadzać ich do plików, zrobić porównanie i odczytać je z powrotem.

Widzę, że wszystkie polecenia comm, diff, cmp pozwalają na przekazanie dwóch plików LUB pliku i standardowego wejścia - myślę, że to dobrze, jeśli nie chcę wyprowadzać dwóch plików ... ale nadal jest do bani.

Grzebałem w kółko myśląc, że mogę użyć grep lub wyrażeń regularnych - ale chyba nie.


1
co tak naprawdę chcesz robić?

Możesz użyć manipulacji podłańcuchami i wbudowanych operacji testowych ze zmianami IFS do porównania, ale musisz wiedzieć, czy chcesz porównać znak po znaku, słowo po słowie, wiersz po wierszu, zignorować spacje ...
technosaurus

Odpowiedzi:


198

Używając difflub comcokolwiek chcesz:

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

Greg's Bash FAQ: Zastępowanie procesów

lub z nazwaną potoką

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

Greg's Bash FAQ: Praca z nazwanymi potokami

Nazwany potok jest również znany jako FIFO.

Sam -w sobie jest przeznaczony dla standardowego wejścia.

<<< jest „ciągiem tutaj”.

&jest jak, ;ale umieszcza to w tle


5
+1 za poprawną odpowiedź. +1 za świetne wyjaśnienie symboli. Dodatkowo, Greg's Bash FAQ został przeniesiony na: mywiki.wooledge.org Linki do powyższych stron są teraz na mywiki.wooledge.org/ProcessSubstitution i mywiki.wooledge.org/BashFAQ/085
timemachine3030

dzięki! a także, to pokaże dynamiczne deskryptory plikówFUNC(){ echo "$@"; "$@"; }; FUNC diff <(echo a) <(echo b);
Aquarius Power

Szukałem tego do zestawienia dwóch shasumów. Nie jestem pewien, czy istnieje bardziej elegancki sposób, ale działa.
fuma

Wydaje się, że działa to, jeśli w $ string1 i $ string2 jest wiele linii, a diff wyświetla linie, które zostały dodane lub odjęte. Co się stanie, jeśli łańcuch jest pojedynczą linią i linią i jest jakaś różnica między tymi dwoma łańcuchami?
alpha_989

@ alpha_989, oto twoja odpowiedź: $ 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. \nUżywanie potoku jest podobne, z tym że pokazuje numer procesu, zaczyna się 1c1od następnego $i czeka, aż naciśniesz <kbd> Enter <kbd> (lub możesz wykonać inne polecenia ...)
bballdave025

19

Przypomina mi to pytanie: Jak można porównać dwa rurociągi w Bash?

Jeśli jesteś w sesji bash, możesz wykonać:

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

z <tworzeniem anonimowych nazwanych potoków - zarządzanych przez bash - dzięki czemu są one tworzone i niszczone automatycznie, w przeciwieństwie do plików tymczasowych.

Więc jeśli uda Ci się wyodrębnić dwa różne ciągi jako część polecenia (grep, awk, sed, ...), możesz zrobić - na przykład - coś takiego:

diff < grep string1 myFile < grep string2 myFile

(jeśli przypuszczasz, że masz w pliku takie linie jak string1=very_complicated_valuei a string2=another_long_and_complicated_value': bez znajomości wewnętrznego formatu pliku nie mogę polecić dokładnego polecenia)


13

Wolę cmpi funkcję zastępowania procesów w bash:

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

Mówiąc na pozycji 2, ab występuje w pierwszej, ale aq w drugiej. Na pozycji 5 zachodzi kolejna różnica. Po prostu zamień te ciągi na zmienne i gotowe.


Działa to tylko wtedy, gdy struny mają taką samą długość!
strpeter

11

Powiedz, że masz trzy struny

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

Aby usunąć przedrostek b z a

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

Aby usunąć przyrostek c z a

echo ${a%"$c"}  # this is

2
Myślę, że to jest podstawowy sposób na zrobienie tego. Ładnie działało. Jednak ta składnia jest nieco trudna do zrozumienia.
Mikael Roos

@MikaelRoos Zgoda. Łatwiejszym do odczytania (w każdym razie dla mnie) byłoby użycie seda: echo "$a" | sed "s!^$b!!g" (Zamieniłem standardowy separator seda / for! Na wypadek, gdyby zmienne, z którymi mamy do czynienia, były ścieżkami. Ponadto możesz użyć tutaj ciągu zamiast echo:. sed ... <<< $a)
ACK_stoverflow,

1

Inny przykład:

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

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

Wyjścia

84192

Oryginalna odpowiedź tutaj

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.