Escaping characters in bash alias [duplicate]
Asked Answered
I

2

14

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.

Illustrate answered 22/3, 2017 at 13:50 Comment(3)
Doesn't really answer your question but since you tagged bash, I'd swap $(pwd | ...) for "${PWD##*/}".Bullate
Related: #20111563, and #40814587Emanuele
BTW, while usually echo is a very poor choice of debugging tool (with several means of munging the data it's intended to display as-is), it actually suffices to show the problem here: You'll see that echo 'tmux new -s $(pwd | tr '\/' '\n' | tail -n 1)' doesn't display any inner quotes, because -- with those quotes being syntactic rather than literal -- they were consumed during the parsing process.Emanuele
E
26

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.

Emanuele answered 22/3, 2017 at 14:28 Comment(0)
M
2

Your embedded single quotes weren't being treated as embedded, they were terminating previous strings and staring new ones. Your first attempt was being treated as the concatenation of tmux new -s $(pwd | tr, \/, ' ', \n, and | tail -n 1). Put those all together and handle the escapes and you get what you see in the output of your type command.

Also worth considering: https://unix.stackexchange.com/questions/30925/in-bash-when-to-alias-when-to-script-and-when-to-write-a-function

Meijer answered 22/3, 2017 at 14:1 Comment(3)
So a bash alias will just concatenate strings until an end line? To me that's unintuitive. Thanks for clearing things up.Illustrate
@JohnStone well, crucially here, there are no tokenizing spaces (I actually left the space out of the list of strings accidentally). If you had unescaped/quoted spaces anywhere in the list, those would be processed as different "words" by bash, here you just have a single word because you have a continuous string in which some parts are quoted, and some are bare strings (like \n for example)Meijer
@JohnStone, ...not just "a bash alias" -- all strings in POSIX-compliant shell languages (not just bash) follow that same rule.Emanuele

© 2022 - 2024 — McMap. All rights reserved.