Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I use `sh -c \"...\"` or `"!f() {... ; }; f" in git alias scripts?

I've been getting my feet wet in writing git aliases that take arguments. I've seen some people running shell scripts with

[alias]
  shAlias = !sh -c \" ... \"

and others running functions with

[alias]
  fAlias = "!f() { ... ; }; f"

It seems like (once I get up to speed on Bash — this isn't where I am yet) any of the things I've thought to do would be doable with either form.

In what circumstances is one preferable over the other?

Beyond feasibility, are there processing differences? Memory use / speed / etc?

like image 699
henry Avatar asked Oct 11 '17 19:10

henry


1 Answers

TL;DR: Use the function approach for the simplest of commands, and switch to an external script once you have any kind of issue with quoting. Just avoid sh -c all together.


We can have a look at the git source. It parses out quotes in a git specific way, executes as a file if the result has no shell metacharacters (including space), or executes the equivalent of execlp("sh", "-c", "result \"$@\"", "result \"$@\"", args...) otherwise.

Based on that, the best way to create an alias is definitely to create an external script and using:

[alias]
 myAlias = !/path/to/script

It requires an external file, but in all other ways it's better:

  • You run with any interpreter you want. You can even run bash code since you ask, while the other forms only allow you to run sh code (the difference is like C++ vs C).
  • No additional shell is started unless you want it to. git will execve your executable directly.
  • No escaping is required. It's just a normal script.
  • No surprises or git knowledge needed. It's just a script.

Out of your suggestions, the runner up is:

[alias]
  fAlias = "!f() { ... ; }; f"

This is less awesome because:

  • You unfortunately can only run with sh, and therefore not use any bash features.
  • A shell is always started, but at least it's just one.
  • Some git specific escaping is required for quotes.
  • You need to be aware of git's escaping, but can generally get by without knowing exactly how it executes the command because you'll just use the function's arguments.

The last one is by far the worst:

[alias]
  shAlias = !sh -c \" ... \"

It's awful because:

  • You can only run with sh.
  • It'll start an extra shell for no good reason.
  • You need both git escaping and also an additional level of sh escaping.
  • You need to know both how to escape code for sh, how to double-escape that with git, and how git appends "$@" to your command if there are additional arguments, and passes additional parameters as $1, $2.

To demonstrate the practical difference, let's say you wanted to make an alias for this command to get the path on a remote server via ssh:

ssh "$1" 'echo "$PATH"'

You can either copy-paste it verbatim to a file, add a shebang, and use:

[alias]
  get-remote-path = !/path/to/my/script

Or you can add a bit of git escaping and use the function approach:

[alias]
  get-remote-path = "!f() { ssh \"$1\" 'echo \"$PATH\"'; }; f"

Or we can laboriously escape and escape again with the sh -c approach (no, it doesn't work without the last word):

[alias]
  get-remote-path = !sh -c 'ssh \"$1\" '\\''echo \"$PATH\"'\\' cthulhu

Clearly, it's better to use a script, or at worst the function approach until you need more complicated commands that you can comfortably quote.

like image 105
that other guy Avatar answered Oct 22 '22 08:10

that other guy