Skip to content
Advertisement

Inconsistent behavior when replacing substring with tilde “~” in a BASH parameter expansion

I came across some strangely inconsistent behavior in BASH parameter expansions across a few different servers, while trying to write a quick function.

On some versions of BASH, to use a tilde in a substring replacement, the tilde must be escaped, or it will be re-expanded to the home directory:

foo=~/data # ~ is expanded to $HOME
bar1="${foo/#$HOME/~}" # returns ~/data
bar2="${foo/#$HOME/"~"}" # returns ~/data
bar3="${foo/#$HOME/~}" # returns /home/user/data

while on other systems, it will not be re-expanded, and attempting to escape the tilde will add the literal escape characters to the string:

foo=~/data # ~ is expanded to $HOME
bar1="${foo/#$HOME/~}" # returns ~/data
bar2="${foo/#$HOME/"~"}" # returns "~"/data
bar3="${foo/#$HOME/~}" # returns ~/data

Note my goal here is to insert the literal string “~”.


The BASH versions where no escape was necessary are here:

GNU bash, version 4.4.12(1)-release (x86_64-apple-darwin16.3.0)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

GNU bash, version 4.2.37(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

The BASH version where an escape was necessary is here:

GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

So what is going on?

Advertisement

Answer

@iBug posted how to ensure we always expand the tilde. To ensure we always get the tilde character itself on any system, use this:

bar="${foo/#$HOME/~}" # returns ~/data or /home/user/data
[[ "$bar" =~ "$HOME"* ]] && bar="${foo/#$HOME/~}"

which redoes the substitution with escaped tilde only if the original non-escaped tilde was expanded.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement