1

I'm writing a bash script to automate some job, and in the meantime practice with bash. I'm writing a script to automate some git cloning and updating stuff. My script will have three options (-g, -f, -h). When someone types -h it should display the help message (which I have written but omitted it below), when someone writes -g, it should accept at least one argument, but it can also accept second one as optional. From which, the first one will be the url of the repository to clone and second one will be the directory, where to clone it. On the other hand, if someone types -f, the script should get just one argument, just a file name. Then I want to read the file, line by line, and do git cloning and updating for each git url inside the file.

If I run the script I get the following error message for each option, and if I call it without any option, or with a valid option followed by some other argument, it still doesn't do anything, just returns:

./gitupdate.sh: option requires an argument -- g 

I guess it doesn't use $2 and $3 somehow in my code. But then again, when I pass -h, all it has to do is call the help function and display the message, doesn't need to use any other argument.

I guess the problem is because I have something wrong at the bottom, where I use getopts to get the option name specified by the user. In my code I assume that the option is the first argument, $1, and then the second one $2 is the url or filename, according to the option specified, and the $3 is the optional one that works only with -g option.

Below you can find my code:

#!/bin/bash declare default_directory=$HOME declare action declare src function clone_repo() { if [ -n "$2" ]; then if [ "$(ls -A "$2" 2>/dev/null)" ]; then cd "$2" else git clone "$1" "$2" fi else git clone "$1" #TODO: Get the directory name created by cloning # and cd to it. fi git remote add upstream "$1" git fetch upstream } function read_repos_from_file() { if [ -f "$1" ]; then while read -r line; do clone_repo "$line" "$2" done < "$1" else echo -e "Error: The specified file could not be found." exit 1 fi } while getopts "f:h:r" option do case "${option}" in f) action=read_repos_from_file; src="$OPTARG";; g) action=clone_repo; src="$OPTARG";; h) help ; exit 1 ;; esac done shift $((OPTIND-1)) [ -z "$action" ] && ( help; exit 1 ) 

If someone could help me, I would be glad.

EDIT: Corrected some typo mistakes in the above code. And updated the error message. The script doesn't work correctly. I guess I need to change something in my code, to make it do something, and actually get $2 and $3 arguments. It even doesn't display the help message, when -h option is passed, and all I do there is call the help function that I have previously created. Maybe I need to somehow change my getopts part.

EDIT 2: Made the advised changes, and changed the above code.

1 Answer 1

1

git() is the beginning of a function definition (the keyword function is optional when the function name is followed by parentheses). If you want to call a function git() you need to define it first, and call it without the parentheses:

function git() { # do stuff } git 

It's not good practice to create functions with the same name as existing binaries, though. In your case you should probably just call git clone with the line read from the file:

while read -r line; do git clone "$line" done < "${file}" 

Edit: Updated, since the question changed significantly.

Your argument processing is … weird, to be frank. When you're using an option parser, you shouldn't work around the way that option parser works. "g:" means an option -g with exactly one argument. Don't try to make it an option with more than one argument, one of them being optional on top of it. If you need an additional (optional) argument for an output directory, make that either another option (e.g. "d:") or a non-option argument.

I'd suggest to change your option-processing to something like this:

while getopts "f:g:h" option; do case "$option" in f) action=file; src="$OPTARG";; g) action=repo; src="$OPTARG";; h) help; exit 1;; esac done shift $((OPTIND-1)) [ -z "$action" ] && ( help; exit 1 ) 

After this "$@" holds only non-option arguments (in this case the optional output directory), so you could call your functions like this:

$action $src "$@" 

with the functions simplified to something like this:

function repo() { if [ -n "$2" ]; then if [ "$(ls -A "$2" 2>/dev/null)" ]; then cd "$2" else git clone "$1" "$2" fi else git clone "$1" fi ... } function file() { if [ -f "$1" ]; then while read -r line; do repo "$line" "$2" done < "$1" else echo "Error: The specified file could not be found." exit 1 fi } 

On a more general note, you should make your names more self-explanatory. Better names for functions for cloning a repository or reading repositories from a file would be something like clone_repo and read_repos_from_file respectively. Also, -c or -r would be better mnemonics for an option that triggers the cloning of a repository.

Sign up to request clarification or add additional context in comments.

10 Comments

For completeness, the function keyword is only optional when you include the parentheses: function git () { (redundant), function git { (non-standard), or git () { (standard).
I corrected the typo that I had. I didn't mean to call git(), I just wanted to call there my previously created function called repo. So I updated it, and change the error message that I get now.
@erkant Why the set detour? You could simply call repo "$line". However, since that function expects 2 arguments, so you have to either define a destination directory when calling the function in file() or make the second argument optional. Plus, your whole processing is overly complicated. See updated answer.
@AnsgarWiechers Thank you very much for your informative and intelligent answer. I made the advised changes and updated my new code. Though, I have some question. Where and how should I use that src variable? About the new function that handles functions. I can pass to my new repo function my default directory as the third argument. But how can I make it optional, and if user specifies directory, to use it as the third argument? Shouldn't I use something like set to overwrite my default directory?
@erkant Not sure if I understand your question. Do you want to use a default directory in case none is passed to the function? You could use parameter expansion for that. dir=$2; dir=${dir:=/default/dir} will assign either $2 or /default/dir (if $2 is undefined) to the variable $dir.
|