Message ID | 20240525234454.1489598-2-iwienand@redhat.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | [v6,1/3] Documentation: alias: rework notes into points | expand |
Ian Wienand <iwienand@redhat.com> writes: > +* If the shell alias is the full path to a binary, it will be executed > + directly with any arguments as positional arguments. > +* If the alias contains any white-space or reserved characters, it > + will be considered an inline script and run as an argument to `sh > + -c`. > +* When running as a script, if arguments are provided to the alias > + call, Git makes them available to the process by appending "$@" to > + the alias shell command. This is not appended if arguments are not > + provided. These are not technically wrong per-se. > +** For "simple" commands, such as calling a single binary > + (e.g. `alias.myapp = !myapp --myflag1`) this will result in any > + arguments becoming additional regular positional arguments to the > + called binary, appended after any arguments specified in the aliased > + command. But the single-command script still receives the arguments in argv[], so what the alias command has to do is the same. The earlier ones that are "not technically wrong" are merely implemenation detail. In a single command case, e.g., "[alias] single = !one", you may write #!/bin/sh echo "$1" | grep "$2" in 'one' script, and "git single 1 2" will be turned into start_command: one 1 2 i.e. one receives two arguments in argv[]. It is an implementation detail that we can bypass "sh" or "-c" or "$@" If you write exactly the same thing like $ git -c 'alias=single=!one ' single 1 2 you'll instead see start_command: /bin/sh -c 'one' "$@" "one " 1 2 because the trailing SP in the alias disables the optimization to bypass a more generic 'sh -c ... "$@"' construction. What gets run is an equivalent of the reader saying $ /bin/sh -c 'one "$@"' "one " 1 2 bypassing git from the command line. What the script (one) has to write does not change at all either case. As I keep saying over multiple iterations, the above three bullet points stress too much on the minute implementation detail while failing to tell readers that the end-user alias receives the rest of the command line as arguments. > +** Care should be taken if your alias script has multiple commands > + (e.g. in a pipeline), references argument variables, or is "argument variables" -> "arguments". > + otherwise not expecting the presence of the appended `"$@"`. "otherwise not expecting" is SIMPLY BUGGY but the readers may not understand it unless you tell them that the arguments are fed to their aliased command by appending them. When you look at the implementation detail of "sh -c '... $@' - $ARGS" as something to fight against, readers would lose sight to see the crux of the problem they are trying to solve. I think it is a wrong way to frame the issue. The problem readers are solving when coming up with their own alias is this: How would one write a single-liner that can take arguments appended at the end? I think giving that to the readers upfront, i.e. "when you write an alias, you are forming a single-liner that takes arguments appended at the end", would go a long way without having them lose sight in the implementation details of "sometimes args directly come in argv[], sometimes your alias is wrapped in "sh -c" and "$@" is used. They do the same thing to feed the arguments to your script. Going back that 'one' example, if 'echo "$1" | grep "$2"' was what you wanted to run, how would you write a single-liner that does echo "$1" | grep "$2" and can take its arguments at the end? You do *not* want to see your invocation of the alias $ git single 1 2 turn into $ echo "$1" | grep "$2" 1 2 of course, and remember, "$@" is merely an implementation detail that the end-users do not need to see. Of course the simplest one-liner, if you had the "one" script already stored in the file, is to say $ one 1 2 i.e. "[alias] single = !one". But calling that a "single-liner" is cheating. You can do one of two easy things. $ sh -c 'echo "$1" | grep "$2"' - 1 2 $ e(){ echo "$1" | grep "$2"; };e 1 2 The earlier string (before "1 2" is appended) of either of these gives you "a single-liner that takes arguments at the end" that does the "echo the first one, pipe it to grep that looks for the second one", which you would make the body of the alias. If the reader understands the earlier example that stores it in a file, the former is more mechanical and straight-forward rewrite of it. The latter may be a bit more convoluted, but says the same thing in the same number of letters. HTH.
On Sun, May 26, 2024 at 04:26:51PM -0700, Junio C Hamano wrote: > As I keep saying over multiple iterations, the above three bullet > points stress too much on the minute implementation detail while > failing to tell readers that the end-user alias receives the rest of > the command line as arguments. OK, I can agree that perhaps I've been a bit to fixated on the addition of "$@" and the mechanics of this. I will propose again with this trimmed. What I didn't have to help me at the time was the full command in GIT_TRACE, which I think is probably a more appropriate way to communicate these details of what's actually hitting the exec calls. > Of course the simplest one-liner, if you had the "one" script > already stored in the file, is to say So the reason I fell into this, and I wonder how much this plays out for others too, is that shipping these workflow bits as stand-alone scripts would mean no !shell tricks required, it's all very logical and I would never have looked at any of this :) However, when all you have is a hammer ... since a git config .inc file was needed for other things, it has been overloaded into essentially being mini package-manger that avoids having to install additional dependencies; one-liner shell script at a time :) > You can do one of two easy things. > > $ sh -c 'echo "$1" | grep "$2"' - 1 2 Ok, I think "sh -c" in ! aliases is a bit confusing, personally. You end up two shells nested deep, and you really have to explain what's going on with $0; it's very easy to miss. > $ e(){ echo "$1" | grep "$2"; };e 1 2 This method, which is used elsewhere in the docs as well, I think makes the most sense. So I've left that in as the example. -i
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt index 40851ef429..f32b86cde3 100644 --- a/Documentation/config/alias.txt +++ b/Documentation/config/alias.txt @@ -27,3 +27,31 @@ it will be treated as a shell command. For example, defining repository, which may not necessarily be the current directory. * `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix` from the original current directory. See linkgit:git-rev-parse[1]. +* If the shell alias is the full path to a binary, it will be executed + directly with any arguments as positional arguments. +* If the alias contains any white-space or reserved characters, it + will be considered an inline script and run as an argument to `sh + -c`. +* When running as a script, if arguments are provided to the alias + call, Git makes them available to the process by appending "$@" to + the alias shell command. This is not appended if arguments are not + provided. +** For "simple" commands, such as calling a single binary + (e.g. `alias.myapp = !myapp --myflag1`) this will result in any + arguments becoming additional regular positional arguments to the + called binary, appended after any arguments specified in the aliased + command. +** Care should be taken if your alias script has multiple commands + (e.g. in a pipeline), references argument variables, or is + otherwise not expecting the presence of the appended `"$@"`. For + example: `alias.echo = "!echo $1"` when run as `git echo arg` will + actually execute `sh -c "echo $1 $@" "echo $1" "arg"` resulting in + output `arg arg`. When writing such aliases, you should ensure + that the appended "$@" when arguments are present does not cause + syntax errors or unintended side-effects. +** A convenient way to deal with this is to write your script + operations in an inline function that is then called with any + arguments from the command-line. For example `alias.cmd = "!c() { + cmd $1 | cmd $2 ; }; c" will allow you to work with separate + arguments. +** Setting `GIT_TRACE=1` can help debug the command being run.
When writing inline shell for shell-expansion aliases (i.e. prefixed with "!"), there are some caveats around argument parsing to be aware of. This series of notes attempts to explain what is happening more clearly. Signed-off-by: Ian Wienand <iwienand@redhat.com> --- Documentation/config/alias.txt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)