2

I've made this script which automates installing/uninstalling git. In my function for testing if git is installed, I use the git --version command and test the return code.

I dislike the stderr output that occurs normally as I'm trying to make a nice custom output. Though I've figured out how to suppress stderr just for this function, I can't seem to reactivate it.

My read prompt is now missing after calling this function.

function CheckGit() {
    exec 3>&2           # link file desc 3 w/ stderr
    exec 2> /dev/null

    SILENT_MODE=$1

    if [[ ! $(git --version) ]]; then
        if [ SILENT_MODE ]; then
            printf "${LT_RED} GIT IS NOT INSTALLED.\n"
        fi
        continue;
    else
        if [ SILENT_MODE ]; then
            printf "${LT_BLUE} GIT IS CURRENTLY INSTALLED.\n"
        fi
        continue;
    fi
    GIT_INSTALLED=$?
    #turn back on the stderr notifications
    exec 2>&3 3>&-      # Restore stdout and close file descriptor #3
}

while true; do
    printf "${LT_BLUE} Menu\n"
    printf " ***********************************************\n"
    printf "${LT_GREEN} a) Check git.\n"
    printf "${LT_GREEN} b) (More to be added)\n"
    printf "${LT_GREEN} c) ...\n"
    printf "${LT_GREEN} d) ...\n"
    printf "${LT_GREEN} h) ...\n"
    printf "${LT_RED} x) Exit.\n"
    printf "\n${NC}"
    read -p "Please make a selection: " eotuyx
    case $eotuyx in
        [Aa]* ) CheckGit true; continue;;
        [Bb]* ) ...; continue;;
        [Cc]* ) ...; continue;;
        [Dd]* ) ...; continue;;
        [Hh]* ) ...; continue;;
        [XxQq]* ) break;;
        * ) -e "\n${NC}" + "Please answer with a, b, c, d, x(or q).";;
    esac
done
4
  • This might be of use to you. unix.stackexchange.com/questions/184804/… Commented Nov 17, 2018 at 23:54
  • you should get rid of those continue inside the CheckGit function. Those kind of "nonlocal gotos" are not only terribly confusing, but they also don't work the same in all shells: try this with dash, bash, zsh and ksh: sh -c 'foo(){ continue; }; for a in 1 2 3; do foo; echo $a; done'
    – user313992
    Commented Nov 18, 2018 at 0:27
  • 1
    also, if [ SILENT_MODE ]; then ... fi will be always true, because SILENT_MODE is never an empty string ;-)
    – user313992
    Commented Nov 18, 2018 at 0:34
  • tl;dr; change the if [[ ... ]] then to if ! git --version >/dev/null 2>&1; then and get rid of all the exec fd juggling.
    – user313992
    Commented Nov 18, 2018 at 0:42

2 Answers 2

4

To reiterate advice given in comments:

  1. Delete the continue statements from the CheckGit function.
    • They aren't necessary,
    • and — what you might not have realized — they cause, not only an immediate return (from the function) to the main loop, but an immediate return to the while true statement at the top of the main loop.  Therefore, as Tomasz pointed out, your exec 2>&3 3>&- statement isn't being executed.

General notes:

  1. if [ SILENT_MODE ] is always true, as mosvy pointed out, because it's just testing whether the string SILENT_MODE is non-null.  You seem to want if [ "$SILENT_MODE" ].
  2. But you may be fooling yourself.  if [ "$SILENT_MODE" ] is true even if $SILENT_MODE is false — all it's doing it testing whether the string is non-null, so calling CheckGit false will still result in the information being displayed.
  3. And, even if you aren't fooling yourself, you may be fooling the person who has to maintain this script next week.  And, yes, that person might be you.  Your apparent logic is “if in silent mode, report extra information”.  That's logically backwards; it would make somewhat more sense to say if [ "$SILENT_MODE" = false ], or else call the variable VERBOSE_MODE.
  4. $? is very ephemeral.  It's always the result of the most recent command.  So, if you do
    if  check whether git is installed ; then
        printf "GIT IS NOT INSTALLED.\n"
    else
        printf "GIT IS CURRENTLY INSTALLED.\n"
    fi
    GIT_INSTALLED=$?
    then GIT_INSTALLED gets the exit status of the printf.  You need to set GIT_INSTALLED earlier.
  5. Your test, if [[ ! $(git --version) ]]; then, tests, not “the return code” (as you say in the question), but whether git --version writes anything to the standard output.  That may be what you want.  That might be the best way to test whether git is installed.  But it might be better (and often, in general, is better) to check the exit status of the command.
  6. A style note: I find if-then-else statements easier to understand if the “true” part is first.  Your script is saying
    if git is not installed
    then
        say that it's not installed
    else (i.e., if it's NOT not installed)
        say that it's installed
    fi
    Double negatives are confusing.
  7. And, yeah; exec 2> /dev/null is handy if you want to suppress the standard error from 42 statements in a row.  If you need to affect only a single command, just put 2> /dev/null (or > /dev/null 2>&1) on that command, as mosvy suggested.
4
  • With newer versions of bash (eg. 4.4.12), the loop state is reset on entering functions, so a stray continue will print an error message instead of returning to the outer loop. shopt -s compat43 is needed in order to restore the older behavior (similar to dash and zsh, but different from ksh)
    – user313992
    Commented Nov 18, 2018 at 2:31
  • I don't entirely understand what you're saying about "the loop state is reset on entering functions", but it's good to see sanity prevailing. I was surprised that continue acted as a non-local goto when semantically, but not syntactically, in a loop. Commented Nov 18, 2018 at 2:48
  • it means that continue and break in a function will work exactly as at the top level; eg. brk(){ break; }; for i in 1 2 3; do brk; echo $i; done will print 1, 2, 3 (and will also warn about the meaningless use of break)
    – user313992
    Commented Nov 18, 2018 at 4:03
  • I'm not allowed to up-vote yet, but this answer helped a great deal.
    – Bretonator
    Commented Nov 18, 2018 at 21:50
1

Your commands look fine, but the logic doesn't cover all cases. Notice that when you continue, the descriptors are not reversed.

Also analyse how this is supposed to work, and how it does indeed:

GIT_INSTALLED=$?

$? is the exit code of the last command performed. Use debugging to see this closer (set -x).

Also, as a beginner, you'd benefit greatly from static-testing your scripts with shellcheck.net.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .