2

I've always developed my shell scripts using parameters, on a daily-basis or even when developing some automation scripts. However, recently I've tried a different approach, exporting environment variables to my scripts.

#!/bin/bash

: ${USER?"Requires USER"}
: ${FIRST_NAME?"Requires FIRST_NAME"}
: ${LAST_NAME?"Requires LAST_NAME"}
: ${EMAIL?"Requires EMAIL"}

set -x

setup_git_account(){
  su - "${USER}" -c "git config --global user.name '${FIRST_NAME} ${LAST_NAME}'"
  su - "${USER}" -c "git config --global user.email '${EMAIL}'"
}

setup_git_account

This ensures a smaller code, easy checks if all the required variables are initialized and also, better understanding of what the script is doing, once all the variables are declared on outside.

export USER='john' && export FIRST_NAME='John' && export LAST_NAME='Doe' && export EMAIL='john.doe@email.com' && setup_git_account.sh

Which could be represented like this if implemented with receiving parameters:

setup_git_account.sh --user 'john' --firstname 'John' --lastname 'Doe' --email 'john.doe@email.com'

However, the last one, would need way more lines of code to implement the getopts switch case, check the passed parameters values, etc.

Anyway, I know we're used to the second approach, but I think the first approach also has several benefits. And I would like to hear more from you, if there's any downside between the presented approaches. And which one should I be using ?

Thanks!

Valter Silva
  • 16,446
  • 52
  • 137
  • 218
  • 3
    With environment variables there's obviously a higher chance of unexpected behavior if not all of them are always set explicitly. They can be set (accidentially) by the system, your bashrc, a script you ran before etc. – hasufell Jan 03 '17 at 10:44
  • 1
    I would recommend using parameters – hek2mgl Jan 03 '17 at 10:47
  • 1
    Use lower-case variable names. `USER`, in particular, is already in use and *will* be set in your environment already. – chepner Jan 03 '17 at 13:10
  • 1
    It would be a major drawback design if all the utilities used environment variables instead of arguments: if two utilities used the same variable name, say `foo`; if you call the first one like `foo=bar utility1`, and then `utility1` needs to call `utility2` without `foo`, then `utility1` would need to unset `foo`. While `utility1` can handle its own variables, things get trickier if `utility1` doesn't know that `utility2` also calls `utility3` that uses a variable `baz` also used by `utility1`. It would be a nightmare to handle! The current design is much safer as it provides some isolation. – gniourf_gniourf Jan 03 '17 at 14:17
  • 1
    It would also be more difficult to chain utilities. `find` comes to mind. How would you deal with `find ... \( -sometest -exec utility1 -someoption {} \; \) -o \( -someothertest -exec utility1 -someotheroption {} \; \)`. – gniourf_gniourf Jan 03 '17 at 14:20
  • @gniourf_gniourf Indeed would be a nightmare! Thank you for pointing that out, I haven't thought about that before. – Valter Silva Jan 04 '17 at 14:49

4 Answers4

2

A bit off-topic, the invocation syntax with environment variables for bash can be shorter, no need for export's:

USER='john' FIRST_NAME='John' LAST_NAME='Doe' EMAIL='john.doe@email.com' setup_git_account.sh
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • This is a good answer. The question of environment-variables-vs-parameters is a bit opinion-based, but this is the right way to call the script if environment variables are used. – chepner Jan 03 '17 at 13:02
1

None of your values is optional; I would just use positional parameters.

: ${1?"Requires USER"}
: ${2?"Requires FIRST_NAME"}
: ${3?"Requires LAST_NAME"}
: ${4?"Requires EMAIL"}

sudo -u "$1" git config --global user.name "$2 $3" user.email "$4"

Providing the way for the user to specify values in an arbitrary order is just an unnecessary complication.

You would simply call the script with

setup_git_account.sh 'john' 'John' 'Doe' 'john.doe@email.com'

Reconsider whether the first and last names need to be separate arguments. They are combined into a single argument to git config by the script anyway; just take the name as a single argument as well.

setup_git_account.sh 'john' 'John Doe' 'john.doe@email.com'

(with the appropriate changes to the script as necessary).

chepner
  • 497,756
  • 71
  • 530
  • 681
0

I never use your approach. I think there are no drawbacks by using parameters. It's a common way to use parameters and if you are using longopts there are self-descriptive. In my opinion env vars are a solution if you need data in different scripts.

Maybe you have problems to run such a script on systems where you don't be allowed to change the environment.

0

I've parameterized your variables using a guide I wrote a while back and even added --help.

This solution accepts environment variables as well as options (which will trump the variables):

while getopts e:f:hl:u:-: arg; do
  case "$arg" in
    e )  EMAIL="$OPTARG" ;;
    f )  FIRST_NAME="$OPTARG" ;;
    h )  do_help ;;
    l )  LAST_NAME="$OPTARG" ;;
    u )  USER_NAME="$OPTARG" ;;
    - )  LONG_OPTARG="${OPTARG#*=}"
         case $OPTARG in
           email=?* )   EMAIL="$LONG_OPTARG" ;;
           first*=?* )  FIRST_NAME="$LONG_OPTARG" ;;
           help* )      do_help ;;
           last*=?* )   LAST_NAME="$LONG_OPTARG" ;;
           user=?* )    USER_NAME="$LONG_OPTARG" ;;
           * ) echo "Illegal option/missing argument: --$OPTARG" >&2; exit 2 ;;
         esac ;;
    * )  exit 2 ;; # error messages for short options already given by getopts
  esac
done
shift $((OPTIND-1))

HELP=" - see ${0##*/} --help"
: ${USER_NAME?"Requires USER_NAME$HELP"}
: ${FIRST_NAME?"Requires FIRST_NAME$HELP"}
: ${LAST_NAME?"Requires LAST_NAME$HELP"}
: ${EMAIL?"Requires EMAIL$HELP"}

su - "$USER_NAME" -c "git config --global user.name '$FIRST_NAME $LAST_NAME'"
su - "$USER_NAME" -c "git config --global user.email '$EMAIL'"

Note that I changed $USER to $USER_NAME to avoid conflicts with your local environment ($USER is your user name on your local Linux system!)

You can also extract the user's full name from the system:

FULL_NAME="$(getent passwd |awk -v u="$USER_NAME" -F: '$1 == u { print $5 }')"

(I see no reason to separate FIRST_NAME and LAST_NAME; what do you do for Jean Claude Van Damme? They're only used together anyway. Also note that not all users will have full names in the passwd file.)

This uses do_help to show the --help output. Here's an example of how that could look (I'd put this at the vary top of the script so somebody just reading it can get the synopsis; it's not in the above code block because I wanted to prevent the block from getting a scroll bar):

do_help() { cat <</help
Usage: ${0##*/} [OPTIONS]
  -u USER_NAME,  --user=USER_NAME
  -f FIRST_NAME, --firstname=FIRST_NAME
  -l LAST_NAME,  --lastname=LAST_NAME
  -e EMAIL,      --email=EMAIL
Each option may also be passed through the environment as e.g. $EMAIL

Code taken from https://stackoverflow.com/a/41515444/519360
/help
}
Adam Katz
  • 14,455
  • 5
  • 68
  • 83