I wrote a custom shell script for myself to make it easier for me to commit code to Github.
Recently, I wanted to start using Github's ability to automatically close issues by including their number in the commit message:
# Would automatically close #1 on push
git add .
git commit -m "Closes issue #1 ..."
git push
However, the way my script is set up, it grabs all the parameters using $* but that automatically removes anything after the # symbol, because that's a comment in shell scripts.
commit() {
# Print out commands for user to see
echo "=> git add ."
echo "=> git commit -m '$*'"
echo "=> git push --set-upstream origin $current_branch"
# Actually execute commands
git add .
git commit -m "$*"
git push --set-upstream origin $current_branch
}
Now I CAN do commit 'Closes issue #1 ...' with the wrapping quotes, but that's slightly annoying...I specifically setup my script so I could easily write: commit Whatever message I want to put in here...
I've looked over the man pages and done some SO searching, but I can't find anything about the specific issue of escaping # symbols as a parameter.
Is this even possible?
Anything after the # is interpreted by the shell as a comment, so it will not passed to the function. This happens before the function is executed. There is nothing the function can do to prevent this.
There are two canonical ways of doing this:
Just require quoting. Every single Unix tool that accepts command line parameters requires this.
Have the program read the commit from stdin instead with read -r input. You can then run just commit and type in the message.
These are both good solutions because they're simple, familiar, transparent, robust, idiomatic Unix that is straight forward to reason about. It's always better to work with Unix than against it.
However, if you instead prefer a complex, unfamiliar, opaque, fragile special case, you can kludge it with magic aliases and history:
commit() {
echo "You wrote: $(HISTTIMEFORMAT= history 1 | cut -d ' ' -f 2-)"
}
alias commit="commit # "
Here's an example of this:
$ commit This is text with #comments and mismatched 'quotes and * and $(expansions)
You wrote: commit This is text with #comments and mismatched 'quotes and * and $(expansions)
just played a little with it
the script
commit() {
echo "$*"
}
script's usage and output
➜ ~ commit "whatever you want #1 for some reason"
whatever you want #1 for some reason
➜ ~ commit 'whatever you want #1 for some reason'
whatever you want #1 for some reason
➜ ~ commit whatever you want \#1 for some reason
whatever you want #1 for some reason
➜ ~ commit whatever you want #1 for some reason
whatever you want
➜ ~
so if you don't want to quote the message you need to escape the hash with a \ (backslash), that is actually a regular escape character
Related
I wrote a bash script to update my files through git automatically, but I keep getting error for the git commit line, if my message contains error.
I have already wrapped my message with quote, what's wrong here?
My bash script:
##Handle local git update
remote='
cd express-demo-nonbare;
git pull origin master;
';
echo "Process for updating Git begin";
git add . ;
read -p "Message for this commit: " comment;
comment=\"${comment}\";
echo $comment;
git commit --message=$comment;
git push backup master;
Really you just need to quote the comment variable when supplying it to git commit. You can replace:
read -p "Message for this commit: " comment;
comment=\"${comment}\";
echo $comment;
git commit --message=$comment;
with
read -p "Message for this commit: " comment;
echo $comment;
git commit --message="$comment";
The quotes will not be part of the commit message, rather they are consumed by bash in ensuring that the entire content of the comment variable is submitted as part of a single --message=... argument to git, even if it contains whitespace characters.
You don't want to, as you put it:
wrap... my message with quote
(which is indeed what you are doing). Instead, you want to protect your message from having bash treat it as a list of words. To do that, you need quotes, but in a different position:
remote='
cd express-demo-nonbare;
git pull origin master;
'
echo "Process for updating Git begin"
git add .
read -p "Message for this commit: " comment
echo "$comment"
git commit --message="$comment"
git push backup master
I removed all non-essential semicolons as well (bash treats the end of a line as the end of the command, unless something like an unclosed parenthesis or brace prevents this).
It's not clear to me what your intent is in setting the variable remote to the literal string newlinecdspaceex...;newline, especially since $remote does not occur later in the script. Note, however, that since this string does include white-space, expanding it outside quotes, as in $remote (vs "$remote" which expands it inside quotes) can trigger further shell actions. For instance:
foo='this; that'
wc $foo
will have the wc program attempt to open files named this; and that, having split $foo at the white spaces into separate words that are then passed to wc. This splitting is actually based on $IFS:
IFS=+
foo='this+that'
wc $foo
tries to open files named this and that. Restoring IFS to its normal setting:
wc $foo
tries to open one file named this+that.
(I sometimes use wc as a program to help show what the actual arguments were, since it tries to open each one as a file name, and spits out the actual file name in any subsequent error message or count.)
Similarly, if the expansion of a variable produces shell glob metacharacters, these will be evaluated after the expansion:
foo='*'
wc $foo
will try to open and read every file and directory in the current directory. Again, double quotes will protect against this:
wc "$foo"
will only try to open and read one file, named *.
I have this simple function in my bashrc file
function aupgrade {
cat ~/.bash_aliases > ~/bash/.bash_aliases
cd ~/bash
git add .
if [[ $1 == "" ]]; then
git commit -m "Update"
else
git commit -m "$1"
fi
git push origin master
cd - 1>/dev/null
}
This function has a purpose, this is the expected behavior:
First, replace the content of the .bash_aliases file in the bash repository with the stdout of cat ~/.bash_aliases
Second, go into the ~/bash directory which is a git respository
Third, stage all changes
Fourth, if when calling the aupgrade function the following argument is nothing, just commit with the "Update" message, but if the user wrote an argument, like aupgrade "New commit!", commit the changes with such argument as the message, git commit -m $1
Fifth, push the changes
Sixth, go back to the previous directory
BUT, it doesnt do that, instead it just does:
First, replace the content of the .bash_aliases file in the bash repository with the stdout of cat ~/.bash_aliases
Second, go into the ~/bash directory which is a git respository
Third, stage all changes
Fourth, commit with the "Update" message although there is an argument
Fifth, push the changes
Sixth, go back to the previous directory
This is weird. This looks product of the git push origin master line. It's not a conditional problem, because when I wrote another function like this but without the git push origin master line it worked as desired.
Why does this happen? Is there any solution?
This is what set -x shows to me, it's really weird
So I have tested your code, and the only problem I could find is that passing your first argument as a string with multiple exclamation marks inserts the same (or previous?) command unless you escape it:
It looks like this is exactly what happened for you, but you had "set -x" ran beforehand, thus it replaced the double exclamation marks with that command.
I'd also recommend storing function arguments in named local variables for cleaner code.
I am trying to write a bash alias to wrap around a Git commit command.
Here is what my typical Git commit looks like.
git commit -am 'Comments in here'
Here is what I have attempted to write as an alias (which would go inside my .bashrc file), so I don't have to write out the whole command every time.
comm(){
git commit -am $1
}
Then I would call it like this: comm 'Comments in here'
However I keep getting this error: fatal: Paths with -a does not make sense.
Anything I'm missing here?
Use More Quotes™ - $1 is being split into words (which have a special meaning in *nix shell scripts). So any words except the first one in your commit message is treated as a filename, which indeed does not make sense with -a.
The -a (automatically add) option is causing the error. If you use it with a path on the git command, you get errors.
The -m option requires an argument, the message, so you either need to make your function manage two arguments or change how you use the function.
To provide a generic git-checkin-with-message function, I'd use something like this:
gcim() {
git commit -m "${1:?'Missing git commit message!'}"
}
Then use it just like git commit -m. For example:
gcim "My cool commit does really good stuff"
Adding files to the commit is almost always better done beforehand. git -a will happily add all files if you happen to use . as an argument. The only thing protecting git commit -a from inserting unwanted files is a properly constructed .gitignore file.
However, if you sometimes want to do a git -a -m "some msg" command, you can make another function:
gciam() {
local msg="${1:?Missing commit message!}"
shift
if (( $# > 0 )) ; then
git commit -m "$msg" $*
else
echo "Missing files to add!"
fi
}
Then, to use:
gciam "added new file" that_file.rb
I am trying to make a git shortcut to commit with a message. Since aliases do not support parameters I came up with this function:
function gcm() { git commit -m "$#" }
My expectation is to commit with a message without even typing quotes like this:
gcm create cli module
However I get an error, probably due to the string interpolation when expanding the all-params symbol.
error: pathspec 'cli' did not match any file(s) known to git.
error: pathspec 'module' did not match any file(s) known to git.
How can I fix the function so I can have an alias that saves me from typing quotes?
UPDATE:
I love this shortcut so much that I must make a copy paste friendly version of the solution for everyone
alias gcm='function() { git commit -a -m "$*" }'
You want "$*" not "$#" because you want the arguments expanded as a single word not multiple words.
This is one of the few times the "$*" expansion is actually desirable. It generally isn't.
Make your commit message a single argument, not a sequence of separate arguments.
gcm () { git commit -m "$1"; }
and invoke it like this:
gcm "My commit message"
This way, you get the exact text you type in your commit message, without the following happening:
Multiple consecutive spaces collapsed to one space
Other whitespace such as tabs and newlines converted to spaces
Characters like * would need to be quoted anyway to prevent special interpretation by the shell.
If the value of IFS is changed for some reason, it will affect the content of your commit message:
$ gcm () { echo "$*"; }
$ gcm my commit message
my commit message
$ IFS=:
$ gym my commit message
my:commit:message
I am trying to make a bash script which will:
Add all changes to git
Commit with a message I pass to the bash script
Push it to the repo
I am trying to do this with:
m=\"$*\"
git add -A
echo git commit -m $m
git push
However, I am getting errors saying error: pathspec 'Q2,' did not match any file(s) known to git. for everything word I pass to the script.
How can I see what bash it actually doing? When I put echo in front of the offending line (which I presume is the commit) I get a correctly form command. If I put it in to the terminal, I runs find.
To see what bash is doing, add -xv options to the shebang line:
#!/bin/bash -xv
The problem is probably the quoting. m=\"$*\" does not do what you want. $m is still split into several words if it contains whitespace, just the first word starts with a doublequote and the last word ends in a doublequote.
Rather, change the offending line to
git commit -m "$m"