Escaping characters in bash alias

Charlie OConor picture Charlie OConor · Mar 22, 2017 · Viewed 8.9k times · Source

This was the alias:

     # make a tmux session in working dir with the name of the dir
     alias th='tmux new -s $(pwd | tr '\/' '\n' | tail -n 1)'  

It doesn't work because of the escape characters or due to the ', single quotes, inside the alias. Printing it out

    $ type --all th
    th is aliased to `tmux new -s $(pwd | tr / n | tail -n 1)'

It looks like it was just stripping out the ' and \.

I eventually fixed it by changing the single quotes to double quotes.

     # make a tmux session in working dir with the name of the dir
     alias th='tmux new -s $(pwd | tr "\/" "\n" | tail -n 1)'  

My question is how did the previous on work at all? Shouldn't bash throw a parsing error.

Answer

Charles Duffy picture Charles Duffy · Mar 22, 2017

Best Advice: Don't.

Use a function instead:

th() { tmux new -s "${PWD##*/}" "$@"; }

${PWD##*/} is a parameter expansion which strips everything up to and including the last / from the contents of $PWD.


Alternate Approach: Literal Quotes

The issue in your original code is that it contains syntactic quotes -- ones parsed by the shell to determine where single-quoted parsing rules begin and end -- in places where what you actually want is literal quotes, ones which are treated as data (and thus become part of the alias).

One way to make these quotes literal would be to use the $'' quoting form instead, which lets you use literal backslashes to escape inner quotes, making them literal rather than syntactic:

alias th=$'tmux new -s $(pwd | tr \'\\\/\' \'\\n\' | tail -n 1)'

Note that when using $'', literal backslashes need to be escaped as well (thus, written as \\ rather than \).


Explanation: Why

The quoting of strings in POSIX shell languages is determined on a character-by-character basis. Thus, in the case of:

'$foo'"$((1+1))"baz

...$foo is single-quoted and thus treated as a literal string, $((1+1)) is double-quoted and thus eligible for being treated as arithmetic expansion, and baz is unquoted -- even though all three of these are concatenated to form a single word ($foo2baz).

These quotes are all syntactic -- they're instructions to the shell -- not literal (which would mean they'd be part of the data to which that string evaluates).


How This Applies To Your Previous Command

In

alias th='tmux new -s $(pwd | tr '\/' '\n' | tail -n 1)'  

...the single quotes in the arguments to tr end the single quotes started at the beginning of the alias. Thus, \/ and \n are evaluated in an unquoted context (in which \/ becomes just /, and \n becomes just n) -- and since, as described above, multiple differently-quoted substrings can just be concatenated into a single larger string, you get your prior command, not an alias.