0

This has me completely stumped.
Basically, when I write a string to a file, and then read it back, the string does not equal itself.
How can I test that the string I read from the file is equal to the expected value?

Example:

➜ foo="foo\tbar"
➜ echo $foo > foo.txt
➜ bar=`head -n 1 foo.txt`

➜ printf $foo | od -x
0000000 6261 6463 6509 6766
0000010

➜ printf $bar | od -x
0000000 6261 6463 6509 6766
0000010

➜ [ $bar != $foo ] && echo "they are not equal ????"
they are not equal ????

It seems to me that the two variables are clearly equal. Why is bash evaluating them as not equal?

If I run the above tests without the \t in the string, the equality works fine, so I thought the tab character has something to do with this.

However, if I set $bar directly to $foo ($bar=$foo) with the tab included, then the equality passes, so it can't be directly a result of there being a tab in the string.

What is going on here? How can I ensure the first line of a file matches a string with tabs?

All tests are run with zsh.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
SimpleSam5
  • 3,280
  • 2
  • 16
  • 13

1 Answers1

2

zsh's implementation of echo conforms to the POSIX standard, so the literal characters \ and t in the value of foo are converted to a literal ASCII tab character when you write the value to foo.txt. printf also processes the \t. You can see that foo and bar are indeed different using declare -p:

% declare -p foo bar
typeset foo='foo\tbar'
typeset bar=$'foo\tbar'

or using printf properly:

192% printf '%s' $foo | od -x
0000000      6f66    5c6f    6274    7261
0000010
192% printf '%s' $bar | od -x
0000000      6f66    096f    6162    0072
0000007

You can also see that the lengths are different:

% echo ${#foo} ${#bar}
8 7

However, you probably wanted a literal tab in the value of foo in the first place:

% foo=$'foo\tbar'
% printf '%s' $foo | od -x
0000000      6f66    096f    6162    0072
0000007
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Ahh, I see. So foo is literally stored as "foo\tbar" (without a tab character). – SimpleSam5 Mar 07 '18 at 20:28
  • For now, my solution is to set a "expected_foo" varialble like expected_foo=\`echo $foo\`, and then use that in my [ ... ] statement. Is there a cleaner way to do this? – SimpleSam5 Mar 07 '18 at 20:31
  • Yes, just use the `$'...'` quoting shown at the end of the answer. – chepner Mar 07 '18 at 20:32
  • 2
    Also, note that `echo "$foo"` in `bash` *probably* outputs a backslash and `t` separately, without replacing the with a literal tab. There are ways to make `bash`'s `echo` POSIX-compliant, but they are all off by default. For predictable results, always use `printf`. – chepner Mar 07 '18 at 20:34