I'm not interested in a "why tcsh is evil" response, this question requires tcsh for it's use. If this is a bug in tcsh, please just let me know that.
I'm generally pretty good at quoting output but I've been stumped by a use case I have to deal with. Here's the simplified case:
I have an executable that I eval the output of. Sometimes it needs to print output (to stdout) so I have tried using echo (builtin), /bin/echo and /bin/printf.
As a simple example, say my executable is called "simpleWrap" and it outputs:
echo "hello world"
So I run (eventually using an alias, but that's irrelevant here):
eval `simpleWrap`
And I get, as expected:
hello world
But here's the problem. Sometimes I need a tilde in the output. So let's try some examples. We'll put tildes in the simpleWrap output (this is not the contents of the script, but what it outputs):
echo "These are tildes: ~ and \~ and \\~"
And surprisingly when I eval the output of simpleWrap now, I get:
These are tildes: /home/dave and \~ and \~
Either the ~ is expanded to my home directory, or the \ protects it, but I can't get rid of the backslash.
How can I just print a '~' in the output of an eval with backticks?
I believe the backticks are forcing the ~ expansion, but they aren't doing it in a consistent way. For example, if I skip the backticks and just do:
eval echo "These are tildes: ~ and \~ and \\~"
Then I get a consistent, expected output:
These are tildes: /home/dave and ~ and \~
Is there something wrong with substitution in backticks, or am I missing the proper quoting possibility? (I've also tried wrapping in single and double quotes to no avail)
The differences arises from the order shell substitutions take place, and the different behavior of parsing the \
, when it is inside double-quotes, as opposed to no double-quotes.
To demonstrate the behavior of \
:
echo \~
=> ~
echo "\~"
=> \~
echo "\\~"
=> \~
eval `simpleWrap`
Here, "simpleWrap
" outputs a "raw" string which includes tildes. The string is then passed unchanged (no substitutions) to the eval command (because that's how backticks work), which basically runs a new shell. So the new shell sees this command line:
echo "These are tildes: ~ and \~ and \\~"
(Note that the quotes are seen by the eval
command).
The output of this command is the one you didn't expect, i.e. both \~
(denote A) and \\~
(denote B) produce the same output. Why?
First of all, neither A nor B is substituted, because tilde substitution only happens if the tilde is the first character in its word, which isn't the case for either.
Now, for A, since \~
is not a known escape sequence (unlike \n
for example), the shell leaves it as is, producing \~
.
For B, \\
is a known escape sequence, so the shell correctly interprets it as \
, then appends the rest of the string to it, producing \~
again.
eval echo "These are tildes: ~ and \~ and \\~"
Here, there's only one command line, which includes the tildes. First, there's the shell substitution, which affects neither A nor B, but in this stage, the quotes drop (they are used for grouping words into a single argument, and are not passed to the command). Then, eval
runs (echo
hasn't run yet), and is pass this as input: echo These are tildes: ~ and \~ and \\~
.
Note the quotes dropped. The output of the command without the quotes is what you expect (see "demonstration" above):
echo These are tildes: /home/dave and \~ and \\~
=> These are tildes: /home/dave and ~ and \~
Drop the quotes!
simpleWrap
should print this:
echo These are tildes: ~ and \~ and \\~
instead of this:
echo "These are tildes: ~ and \~ and \\~"