7

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?

henry
  • 4,244
  • 2
  • 26
  • 37

2 Answers2

10

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.

that other guy
  • 116,971
  • 11
  • 170
  • 194
  • Don't know if the Cthulhu reference is a joke or not… guess when I figure out the answer I'll know I'm a real shell scripter ;P – henry Oct 12 '17 at 13:49
  • If I want to specify a file and function with parameters, is this the best way to do it? `co-rb = !bash -c 'source $HOME/.gitconfig.kat.aliases.script && checkoutRemoteBranch $@ "dummy"'`? – Terry Mar 14 '19 at 10:28
  • Also, trying to figure out how to call `cd` from within my custom script file/function and have it apply to the 'current' shell. Is that possible? – Terry Mar 15 '19 at 23:01
  • @Terry This answer goes into great detail about why `!bash -c` is *not* the best way to do anything in a git alias – that other guy Mar 15 '19 at 23:03
  • Thanks...guess I didn't know !sh and !bash is 'same'. Do you know how I could call a custom function in my external bash script? Or do I need to have one script per 'function/alias'? – Terry Mar 15 '19 at 23:26
  • You can pass arguments and have the script do whatever it needs to based on them – that other guy Mar 15 '19 at 23:28
  • Any chance you can guide me on the syntax? I was simply following https://stackoverflow.com/a/46435987/166231 as an example which explained the `!bash -c` concept. My alias is current defined as `undo = !bash -c 'source $HOME/.gitconfig.kat.aliases.script && undoFile "$0" "$@"'` – Terry Mar 15 '19 at 23:46
  • To clarify, my function `undoFile` works fine. I just need to know how to set up the alias in .gitconfig the 'most effective' way to an external script file / specific function / pass all the params. Let me know if you'd like me to open a separate question. – Terry Mar 16 '19 at 00:29
  • Opening a new question would probably be more effective than commenting on an existing one – that other guy Mar 17 '19 at 18:14
2

Prefer the function if applicable, as it is executed directly by whatever shell executes the alias. Using sh -c '...' causes yet another process to be started.

chepner
  • 497,756
  • 71
  • 530
  • 681