3

I have the following git alias function that moves files into a directory using git mv of those files and then does the git commit :

[alias]
mv-into-dir = "!mvIntoDIR() { \
cd ${GIT_PREFIX:-.}; \
allArgsButLast=\"${@:1:$#-1}\"; \
lastArg=\"${@: -1}\"; \
git mv -v $allArgsButLast $lastArg/; \
git commit -uno $allArgsButLast $lastArg -m \"Moved $allArgsButLast into $lastArg/\"; \
}; mvIntoDIR"

It seems fine :

$ git config alias.mv-into-dir
!mvIntoDIR() { cd ${GIT_PREFIX:-.}; allArgsButLast="${@:1:$#-1}"; lastArg="${@: -1}"; git mv -v $allArgsButLast $lastArg/; git commit -uno $allArgsButLast $lastArg -m "Moved $allArgsButLast into $lastArg/"; }; mvIntoDIR
$

But when I run it, I get a 1: Bad substitution error :

$ git mv-into-dir .conkyrc.back tmp
mvIntoDIR() { cd ${GIT_PREFIX:-.}; allArgsButLast="${@:1:$#-1}"; lastArg="${@: -1}"; git mv -v $allArgsButLast $lastArg/; git commit -uno $allArgsButLast $lastArg -m "Moved $allArgsButLast into $lastArg/"; }; mvIntoDIR: 1: Bad substitution
$

3 Answers 3

5

Aliases with ! are run through /bin/sh – not through your user's default shell. On some distributions, /bin/sh is Dash, which does not support array-range expansions like ${@:start:end}.

This is easiest to solve by making your mega-alias into a proper shell script (all commands named git-* automatically become git subcommands), and using #! to specify the script interpreter:

~/bin/git-mv-into-dir
#!/usr/bin/env bash # quote the path to prevent issues with spaces in paths cd "${GIT_PREFIX:-.}" # use an array variable to preserve individual args allArgsButLast=( "${@:1:$#-1}" ) lastArg=${@:(-1)} git mv -v "${allArgsButLast[@]}" "$lastArg/" && git commit -uno "$@" -m "Moved ${allArgsButLast[*]} into $lastArg/"
Sign up to request clarification or add additional context in comments.

4 Comments

I think there is proper POSIX way to do this without splitting arguments between allArgsButLast and lastArg only for printing.
If you're targeting a baseline POSIX system, possibly, but here OP has tagged their question with 'bash' so I assume they intended to target Bash specifically, which is as proper as writing for any other programming language in a known environment.
@lea-gris How do you write git commit -uno "$@" -m "Moved ${allArgsButLast[*]} into $lastArg/" in Dash language ?
Like this: m=Moved; while [ $# -gt 1 ]; do m="$m $1"; shift; done; m="$m into $1"; commit -uno -m "$m"
4

What would git(1) do? Or, What does git(1) do?

! tells it to run your alias in a shell. What shell? It can’t use a specific one like Ksh or Zsh. It just says “shell”. So let’s try /bin/sh:

#!/bin/sh

mvIntoDIR() {
cd ${GIT_PREFIX:-.};
allArgsButLast="${@:1:$#-1}";
lastArg="${@: -1}";
git mv -v $allArgsButLast $lastArg/;
git commit -uno $allArgsButLast $lastArg -m "Moved $allArgsButLast into $lastArg/"; \
};

mvIntoDIR

We get the same error but a useful line number:

5: Bad substitution

Namely:

allArgsButLast="${@:1:$#-1}";

Okay. But this is a Bash construct. So let’s change it to that:

#!/usr/bin/env bash

mvIntoDIR() {
cd ${GIT_PREFIX:-.};
allArgsButLast="${@:1:$#-1}";
lastArg="${@: -1}";
git mv -v $allArgsButLast $lastArg/;
git commit -uno $allArgsButLast $lastArg -m "Moved $allArgsButLast into $lastArg/"; \
};

mvIntoDIR

We get a different error:

line 5: $#-1: substring expression < 0

Okay. So git(1) must be running a shell which does not even know what ${@:1:$#-1} means. But Bash at least recognizes that you are trying to use a construct that it knows, even if it is being misused in some way.

Now the script is still faulty. But at least it can be fixed since it is running in the shell intended for it.

I would either ditch the alias in favor of a Bash script or make a Bash script and make a wrapper alias to run it.

Comments

3

A POSIX-shell implementation that will therefore not require the use of Bash:

#!/bin/sh

: "${2:?Needs at least a destination}"
git mv -v -- "$@" || exit # On fail exit with the git error code if any

# Composes the commit message with all sources and destination
m=Moved # Message start
while [ $# -gt 1 ] # Iterates all arguments except the last one
do
  m="$m $1" # Append argument to the message string
  shift
done
m="$m into $1" # Suffixes message with the last argument

# Performs the commit with given message
git commit -uno -m "$m" || exit # On fail exit with the git error code if any

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.