Bash – way to get *actual* (uninterpreted) shell arguments in a function or script

bashquotingshell

I have a function posix that I use in the Git bash shell on Windows to transform DOS-style paths to normal Unix-style paths. Since DOS-style paths use a backslash as separator, I have to quote the path argument to prevent the backslashes as being used by the shell to denote the next character as a literal. Is there any way to get the uninterpreted argument from inside my function, so that I don't need to quote it?

Here is my function, if it helps:

function posix() {
  echo $1 | sed -e 's|\\|/|g' | sed -re 's|^(.)\:|/\L\1|'
}

(By the way, I welcome any comments with tips for improving the function in other ways unrelated to solving the quoting/shell-interpretation problem.)

Best Answer

The uninterpreted shell arguments are $1, $2, etc. You need to put their expansion in double quotes in most contexts, to avoid the value of the parameter being expanded further. "$@" gives you the list of all parameters.

For example, if you want to pass an argument of the shell script to your function, call it like this:

first_argument_as_filename_in_unix_syntax=$(posix "$1")

The double quotes are necessary. If you write posix $1, then what you're passing is not the value of the first parameter but the result of performing word splitting and globbing on the value of the first parameter. You will need to use proper quoting when calling the script, too. For example, if you write this in bash:

myscript c:\path with spaces\somefile

then the actual, uninterpreted arguments to myscript will be c:path, with and spacessomefile. So don't do this.

Your posix function is wrong, again because it lacks double quotes around $1. Always put double quotes around variable and command substitutions: "$foo", "$(foo)". It's easier to remember this rule than the exceptions where you don't actually need the quotes.

echo does its own processing in some cases, and calling external processes is slow (especially on Windows). You can do the whole processing inside bash.

posix () {
  path="${1//\\//}"
  case "$path" in
    ?:*) drive="${p:0:1}"; drive="${drive,}"; p="/$drive/${p:2}";;
  esac
  printf %s "$p"
}

The zsh feature that jw013 alluded to doesn't do what you seem to think it does. You can put noglob in front of a command, and zsh does not perform globbing (i.e. filename generation, i.e. expansion of wildcards) on the arguments. For example, in zsh, if you write noglob locate *foo*bar*, then locate is called with the argument *foo*bar*. You'd typically hide the noglob builtin behind an alias. This feature is irrelevant for what you're trying to do.