139

I'm trying to find a way to determine the difference between two strings in my script. I could easily do this with diff or comm, but I'm not dealing with files and I'd prefer not to output them to files, do the compare and read it back.

I see that comm, diff, cmp all allow to pass either two files OR a file and standard input - I guess that's good if I don't want to output two files...but it's still kinda sucks.

Been digging around thinking I can use grep or regular expressions - but I guess not.

codeforester
  • 39,467
  • 16
  • 112
  • 140
  • 1
    what is it you actually want to do? –  Jan 18 '09 at 02:51
  • You can use substring manipulations and builtin test operations with IFS changes to compare, but you would need to know if you want to compare character by character, word by word, line by line, ignore white space ... – technosaurus Jul 21 '12 at 10:33
  • See https://stackoverflow.com/questions/34376884/highlight-string-differences – mosh Mar 10 '18 at 17:20

6 Answers6

241

Using diff or com or whatever you want:

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

Greg's Bash FAQ: Process Substitution

or with a named pipe

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

Greg's Bash FAQ: Working with Named Pipes

Named pipe is also known as a FIFO.

The - on its own is for standard input.

<<< is a "here string".

& is like ; but puts it in the background

Stefan van den Akker
  • 6,661
  • 7
  • 48
  • 63
Ian Kelling
  • 9,643
  • 9
  • 35
  • 39
  • 5
    +1 for correct answer. +1 for great explanation of symbols. Additionally, Greg's Bash FAQ has moved to: http://mywiki.wooledge.org/ The links for the above pages are now at http://mywiki.wooledge.org/ProcessSubstitution and http://mywiki.wooledge.org/BashFAQ/085 – timemachine3030 Feb 20 '13 at 17:30
  • thx! and also, this will show the dynamic file descriptors `FUNC(){ echo "$@"; "$@"; }; FUNC diff <(echo a) <(echo b);` – Aquarius Power Apr 16 '15 at 01:30
  • I was looking for that for compairing two shasums. Not sure if there is a more elegant way to do that, but it works. – fuma Aug 23 '16 at 08:29
  • This seems to work if there are multiple lines in $string1 and $string2, and diff outputs the lines that were added or subtracted. What if the string is a single line, and line and there is some difference between the two strings? – alpha_989 Aug 10 '17 at 15:28
  • @alpha_989 , here's your answer: `$ 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.` `\n` Using the pipe is similar, except it shows a process number, starts with the `1c1` after the next `$`, and waits until you press Enter (or you can do other commands...) – bballdave025 May 23 '19 at 18:28
  • `diff <(echo "$string1" ) <(echo "$string2")` -> on macOS, I can run this directly on the command line, but if I try to use it in a script I get `syntax error near unexpected token \`('` – Rafael Eyng Jan 28 '20 at 12:28
23

Reminds me of this question: How can you diff two pipelines in Bash?

If you are in a bash session, you could do a:

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

with < creating anonymous named pipes -- managed by bash -- so they are created and destroyed automatically, unlike temporary files.

So if you manage to isolate your two different string as part of a command (grep, awk, sed, ...), you can do - for instance - something like:

diff < grep string1 myFile < grep string2 myFile

(if you suppose you have in your file lines like string1=very_complicated_value and a string2=another_long_and_complicated_value': without knowing the internal format of your file, I can not recommend a precise command)

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
17

I prefer cmp and Process Substitution feature of bash:

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

Saying at position 2, a b occurs for the first, but a q for the second. At position 5, another difference is happening. Just replace those strings by variables, and you are done.

polynomial_donut
  • 303
  • 1
  • 4
  • 13
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
13

Say you have three strings

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

To remove prefix b from a

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

To remove suffix c from a

echo ${a%"$c"}  # this is
Pithikos
  • 18,827
  • 15
  • 113
  • 136
  • 2
    I guess this is the bash way of doing it. It worked nicely. That syntax is a bit hard to grasp though. – Mikael Roos Nov 05 '14 at 20:39
  • @MikaelRoos Agreed. Easier to read (for me anyway) would be to use sed: `echo "$a" | sed "s!^$b!!g"` (I swapped out the standard sed separator / for ! in case the variables being dealt with are paths. Also, you could use a here string instead of echo: `sed ... <<< $a`.) – ACK_stoverflow Sep 08 '16 at 18:06
2

Another example:

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

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

Outputs

84192

Original answer here

Sida Zhou
  • 3,529
  • 2
  • 33
  • 48
0

if you want to type less characters, try diff <(<<< $s1) <(<<< $s2):

$ s1="xxxxx"
$ s2="yyyyy"
$ diff <(<<< $s1) <(<<< $s2)

P.S., please use diff <(<<< "${s1}") <(<<< "${s2}") for robustnesses.

Weike
  • 1,232
  • 12
  • 15