-1

I encountered some unexpected behaviour of my bash. This is my in-/output:

nepumuk@pc:~$  type URL
URL is a function
URL () 
{ 
    echo -e "${_//%/\\x}"
}
nepumuk@pc:~$  URL %2f
URL
nepumuk@pc:~$  URL %2f
/
nepumuk@pc:~$  URL %2F
/
nepumuk@pc:~$  type URL
URL is a function
URL () 
{ 
    echo -e "${_//%/\\x}"
}
nepumuk@pc:~$   URL %2f
URL
nepumuk@pc:~$  URL %2f
/
nepumuk@pc:~$  URL %2f
/
nepumuk@pc:~$  URL %2f
/
nepumuk@pc:~$    URL %2f
/
nepumuk@pc:~$   URL %2f
/

The corresponding part in the bash function file is:

url() {
        : "${*//+/ }"
        echo -e "${_//%/\\x}"
}

export -f url

URL() {
        echo -e "${_//%/\\x}"
}

export -f URL

During testing I changed the content of URL to echo -e "${@//%/\\x}" to see wether something changes. Opening a new terminal (emulator?) window with the latter being used, I get

nepumuk@pc:~$  URL %2f
/usr/share/bash-completion/bash_completion
nepumuk@pc:~$  URL %2f
/
nepumuk@pc:~$  URL %2f
/

Although I could fully reproduce (and use the former for it gives the desired output), but I'd be happy having an explanation.

4
  • What's unexpected, and what did you expect? Commented Oct 28, 2023 at 17:40
  • 2
    And probably more importantly, what are you actually trying to achieve here? Commented Oct 28, 2023 at 17:44
  • 5
    $_ is expanding to the argument of the previous command - so not the "same input" Commented Oct 28, 2023 at 17:56
  • 1
    on the other hand, $1 would be the first argument to the function itself, and "$@" would be all of them. It should work ok with echo -e "${@//%/\\x}", the output you show doesn't seem to match that expansion
    – ilkkachu
    Commented Oct 28, 2023 at 19:27

1 Answer 1

3

In bash the $_ variable expands to the last argument of the previously run command (among other things).

url() {
   : "${*//+/ }"
   echo -e "${_//%/\\x}"
}

Makes some sort of sense as the expansion of ${*//+/ } is first passed as one any only one argument to the : no-op command.

That's intended to replace +s with spaces in the positional parameters and concatenate the resulting strings with the first character of $IFS inbetween (the usual effect of $* when quoted).

Then the second command takes that resulting string, replaces the %s with \x in it and passes the result to echo.

In bash, when the posix and xpg_echo options are not both enabled, its echo builtin supports a -e option which can be used to expand some escape sequences, some of those are \xHH which expands to the byte with value corresponding to the 0xHH hexadecimal number. So it will replace %HH with the corresponding byte value so in effect do URI %XX decoding.

If you remove the first : command though, $_ in the second command will be whatever last argument was passed to the command run before that function was invoked.

Here it seems whoever wrote that function used that $_ trick to avoid having to use a temporary variable. That however makes the code unnecessarily complicated and hard to read and understand.

uri_decode() {
  local IFS=' ' # make sure arguments are joined with spaces rather than
                # make some assumption on what $IFS may currently contain

  local tmp="$*" # positional parameters joined with spaces

  tmp=${tmp//+/ } # replace +s with spaces

  tmp=${tmp//[%]/\\x} # replace % with \\x. Using [%] instead of % for
                      # increased portability.

  printf '%b\n' "$tmp" # pass the result to printf for expanding of the \x
                       # sequences, avoiding echo whose behaviour depends on
                       # the environment, and would give incorrect results
                       # for strings like -Enee
}

And if you want a variant that doesn't do the + -> space translation, just remove the tmp=${tmp//+/ } part.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .