Bash Case/Switch Formatting - bash

I'm trying to write a case/switch statement in my bash script as follows:
case "$REPLY" in
E*|e*) $EDITOR "$COMMIT_MSG_FILE" < $TTY; continue ;;
Y*|y*) exit 0 ;;
N*|n*) exit 1 ;;
*) SKIP_DISPLAY_WARNINGS=1; create_prompt; continue ;;
esac
However, I keep getting
syntax error near unexpected token ';;'
E*|e*) $EDITOR "$COMMIT_MSG_FILE" < $TTY; continue ;;'
From reading around, I know that ;; is the equivalent of a break statement in a traditional switch statement, but I'm not sure why I'm getting a syntax error here. All of the functions and variables are defined above, so I can't see that being an issue. Any advice?
EDIT: Entirety of the loop:
while true; do
read_commit_message
check_commit_valid
# if there are no warnings, then the commit is good and we can exit!
test ${#WARNINGS[#]} -eq 0 && exit 0;
# if we're still here, there are warnings we need to display
show_warnings
# if non-interactive don't prompt and exit with an error
# need interactivity for the prompt to show and get response
if [ ! -t 1 ] && [ -z ${FAKE_TTY+x} ]; then
exit 1
fi
# show message asking for proceed, etc
echo -en "${BLUE}Proceed with commit? [e/y/n/?] ${NO_COLOR}"
# read the response
read REPLY < "$TTY"
# Check if the reply is valid
case "$REPLY" in
E*|e*) $EDITOR "$COMMIT_MSG_FILE" < $TTY; continue ;;
Y*|y*) exit 0 ;;
N*|n*) exit 1 ;;
*) SKIP_DISPLAY_WARNINGS=1; create_prompt; continue ;;
esac
done

Related

Case statement not working in bash, conditions not apply

Case statement not working. Pressing Enter(empty string) not make script exit, other cases not working too. No one exit 1 commands run when it should, all cases fails when I type text specially for it.
I find out what case works, but exit 1 statement in it not exits the script. How to exit script in that place correctly?
#!/bin/bash
...
get_virtual_host() {
if [ -t 0 ]; then
read -p "Create virtualhost (= Folder name,case sensitive)" -r host
else
# same as 'read' but for GUI
host=$(zenity --forms --add-entry=Name --text='Create virtualhost (= Folder name,case sensitive)')
fi
case "$host" in
"") notify_user "Bad input: empty" ; exit 1 ;;
*"*"*) notify_user "Bad input: wildcard" ; exit 1 ;;
*[[:space:]]*) notify_user "Bad input: whitespace" ; exit 1 ;;
esac
echo "$host"
}
host=$(get_virtual_host)
Addition to clarify:
notify_user () {
echo "$1" >&2
[ -t 0 ] || if type -p notify-send >/dev/null; then notify-send "$1"; else xmessage -buttons Ok:0 -nearmouse "$1" -timeout 10; fi
}
The function is in fact written correctly. It's how it's called that's the problem.
host=$(get_virtual_host)
When you capture a command's output the command runs in a subshell. Exiting the subshell doesn't directly cause the parent shell to exit; the parent shell needs to check the subshell's exit status.
host=$(get_virtual_host) || exit
This will exit the parent if get_virtual_host fails. A bare exit without an explicit exit code forwards the existing value of $?.

Bash script programming

I want to keep printing in a while loop(this occurs every second , i.e. sleeps for one second) and keep asking for a user input in a separate while loop, however if one indefinite runs , it doesn't go to the other for loop and if we run one in background and one in foreground still it doesnt helps, I am programming in bash script
#!/bin/bash
memusageStatus=false
diskspace=false
processStatus=false
menuStatus=true
function printMenu {
while [ true ]
do
if $menuStatus ; then
printf "\na) Show/Hide Memory Usage Information \nb) Show/Hide Disk Space Information \nc) Show/Hide Process Information\no) Show/Hide List of options \nq) Exit\n"
fi
sleep 1
tput cup 0 0 ;
tput ed
done
}
function disMenu {
while [ true ]
do
read ab
echo ab
case $ab in
'a') if $memusageStatus ; then
# free -k
memusageStatus=false;
else
memusageStatus=true;
fi
;;
'b') if $diskspace ; then
diskspace=false
else
diskspace=true;
fi
# df -h
;;
'c') if $processStatus ; then
processStatus=false
else
processStatus=true;
fi
;; #ps u
'o') echo "you pressed o"
if $menuStatus ; then
menuStatus=false;
else
menuStatus=true;
fi ;;
'q') exit 0;;
esac
done
}
printMenu &
disMenu
exit 0
You can try to use a read -t 1 in only one loop instead of a sleep 1.
But maybe with a longer read time.
Something like:
while [ true ]
do
echo "Show your Menu"
read -t 5 ab
case $ab in
'q') exit;;
esac
done

Bash pulling elements from a function

I'm curious to know if you can pull lines from a function in bash. Say I have this function:
error_fn()
{
echo "You need at least 2 command line arguments!"
echo "Program existing because you said you had a typo, please try agian"
echo "Sorry, one or both of the files that you entered was a directory, please try agian"
echo "Sorry, one or both files were not located, please try again"
}
Is there a way to pull the first echo statement (echo "You need at least 2 command line arguments!") from this array?
I have tried using:
error_fn $1
error_fn ("$1")
but this seems to just output all the echo statements in the function. Any ideas?
You could do your error_fn() similar to the following and pass the index value idx (zero-based, e.g. 0 - 3) as the argument to error_fn()to have the corresponding error output.
error_fn() {
array=( "You need at least 2 command line arguments!"
"Program existing because you said you had a typo, please try agian"
"Sorry, one or both of the files that you entered was a directory, please try agian"
"Sorry, one or both files were not located, please try again" )
idx=$(($1))
if [ '0' -le "$idx" -a "$idx" -le '3' ]
then
printf "error: %s\n" "${array[idx]}"
else
printf "error: function index '%d' out of range.\n" "$idx"
fi
}
Look it over and let me know if you have questions.
As for a general error/usage function, I generally prefer as much of the boiler-plate as possible being in a heredoc while having the ability to pass an error message as well through $1 for the funciton. Example:
usage() {
local ecode=${2:-0} ## set exit code '0' by default
test -n "$1" && printf "\n %s\n" "$1" >&2 ## print error msg (if any)
## heredoc of usage information
cat >&2 << USG
usage: ${0//*\//} _required_input_, etc..
This script ... (give description)
Options:
-h | --help program help (this file)
USG
exit $ecode;
}
It use would be to display usage information by default, display an error in addition to the usage if an error string is given as agrument 1, and finally to set the exit code for the program 0 by default but it will set any numeric argument as the exit code if passed as the second argument. Examples
[ -z "$1" ] && usage "warning: insufficient input." # General usage use.
[ -d "neededdir" ] || mkdir -p neededdir # error on exit and set ecode 2
[ -d "neededdir" } || usage "error: unable to create 'neededdir'." 2
The function is just another command creating output. You may parse it.
error_fn | head -n 1
However, if you rewrite the function to take an argument and to display an error message relating to that argument, it will probably do what you intend it to do:
function error_fn {
err="$1"
case "$err" in
EREFP) echo "Error: refining particles failed" >&2 ;;
ECYCE) echo "Error: number of cycles exceeded" >&2 ;;
EHUPM) echo "Error: hangup misdirected, dangling receiver" >&2 ;;
*) echo "Error: something's wrong (code:$err)" >&2 ;;
esac
}
if ! refine_particles; then
error_fn EREFP
else if ! hang_up_projections; then
error_fn EHUPM
fi

bash case statement with unmatched patterns

Ok, so i have tried searching for this on google and i cant seem to find an answer. What i'm trying to do is create a case statement in bash but if the user enters a different number than listed it just exits the script. How do i make it give an error and then ask for the user to select one of the options?
for example, my case statement
case $ans in
1) echo "Running Project 1..."
sleep 2
./project1.sh
;;
2) echo "Running Project 2..."
sleep 2
./project2.sh
;;
Qq) echo "Exiting"
exit
;;
esac
so any options other than 1, 2, Qq it will give an error saying invalid selection, try again.
You need a while loop and a boolean variable like that:
flag = true
while [ $flag ]; do
case $ans in
1) echo "Running Project 1..."
sleep 2
./project1.sh
;;
2) echo "Running Project 2..."
sleep 2
./project2.sh
;;
Qq) echo "Exiting"
flag = false
;;
esac
done

Elegant way for verbose mode in scripts?

When I write bash scripts I usually get the verbose mode this way (simplified):
_V=0
while getopts "v" OPTION
do
case $OPTION in
v) _V=1
;;
esac
done
and then every time I want a "verbose output" I type this:
[ $_V -eq 1 ] && echo "verbose mode on" || echo "verbose mode off"
or for example this:
[ $_V -eq 1 ] && command -v || command
Is there a way to do it more elegant? I was thinking about defining a function named "verbose" and type it instead of [ $_V -eq 1 ], but this would only be a tiny improvement.
I'm sure, there is more common way to do it…
As you noticed, you can define some log functions like log, log_debug, log_error, etc.
function log () {
if [[ $_V -eq 1 ]]; then
echo "$#"
fi
}
It can help increasing your main code readability and hide show\nonshow logic into logging function.
log "some text"
If _V(global variable) is equal 1 "some text" will be printed, in other case it will not.
After reading all other posts I came up with this
# set verbose level to info
__VERBOSE=6
declare -A LOG_LEVELS
# https://en.wikipedia.org/wiki/Syslog#Severity_level
LOG_LEVELS=([0]="emerg" [1]="alert" [2]="crit" [3]="err" [4]="warning" [5]="notice" [6]="info" [7]="debug")
function .log () {
local LEVEL=${1}
shift
if [ ${__VERBOSE} -ge ${LEVEL} ]; then
echo "[${LOG_LEVELS[$LEVEL]}]" "$#"
fi
}
Then you can simply use it like this
# verbose error
.log 3 "Something is wrong here"
Which will output
[error] Something is wrong here
#!/bin/bash
# A flexible verbosity redirection function
# John C. Petrucci (http://johncpetrucci.com)
# 2013-10-19
# Allows your script to accept varying levels of verbosity flags and give appropriate feedback via file descriptors.
# Example usage: ./this [-v[v[v]]]
verbosity=2 #Start counting at 2 so that any increase to this will result in a minimum of file descriptor 3. You should leave this alone.
maxverbosity=5 #The highest verbosity we use / allow to be displayed. Feel free to adjust.
while getopts ":v" opt; do
case $opt in
v) (( verbosity=verbosity+1 ))
;;
esac
done
printf "%s %d\n" "Verbosity level set to:" "$verbosity"
for v in $(seq 3 $verbosity) #Start counting from 3 since 1 and 2 are standards (stdout/stderr).
do
(( "$v" <= "$maxverbosity" )) && echo This would display $v
(( "$v" <= "$maxverbosity" )) && eval exec "$v>&2" #Don't change anything higher than the maximum verbosity allowed.
done
for v in $(seq $(( verbosity+1 )) $maxverbosity ) #From the verbosity level one higher than requested, through the maximum;
do
(( "$v" > "2" )) && echo This would not display $v
(( "$v" > "2" )) && eval exec "$v>/dev/null" #Redirect these to bitbucket, provided that they don't match stdout and stderr.
done
# Some confirmations:
printf "%s\n" "This message is seen at verbosity level 3 and above." >&3
printf "%s\n" "This message is seen at verbosity level 4 and above." >&4
printf "%s\n" "This message is seen at verbosity level 5 and above." >&5
I also came up with this function to do a quick ifelse:
function verbose () {
[[ $_V -eq 1 ]] && return 0 || return 1
}
This executes a command if $_V is set to 1. Use it like this:
verbose && command #command will be executed if $_V == 1
or
verbose && command -v || command # execute 'command -v' if $_V==1, else execute 'command'
If you want to avoid doing an "if" statement every single time you want to log something, you can try this approach (which is how I do it).
The idea is that instead of calling log, you call $echoLog instead. So, if you are in verbose mode, $echoLog will just be echo, but in non-verbose mode, it is a function that prints nothing and just ignores the arguments.
Here's some code you can copy.
# Use `$echoLog` everywhere you print verbose logging messages to console
# By default, it is disabled and will be enabled with the `-v` or `--verbose` flags
declare echoLog='silentEcho'
function silentEcho() {
:
}
# Somewhere else in your script's setup, do something like this
while [[ $# > 0 ]]; do
case "$1" in
-v|--verbose) echoLog='echo'; ;;
esac
shift;
done
Now, you can just drop lines like $echoLog "Doing something verbose log worthy" anywhere you want.
A first try at a more flexible system with verbosity levels (Bash 4):
# CONFIG SECTION
# verbosity level definitions
config[verb_levels]='debug info status warning error critical fatal'
# verbosity levels that are to be user-selectable (0-this value)
config[verb_override]=3
# user-selected verbosity levels (0=none, 1=warnings, 2=warnings+info, 3=warning+info+debug)
config[verbosity]=2
# FUNCTION DEFINITIONS SECTION
_messages() {
# shortcut functions for messages
# non overridable levels exit with errlevel
# safe eval, it only uses two (namespaced) values, and a few builtins
local verbosity macro level=0
for verbosity in ${config[verb_levels]}; do
IFS="" read -rd'' macro <<MACRO
_$verbosity() {
$( (( $level <= ${config[verb_override]} )) && echo "(( \${config[verbosity]} + $level > ${config[verb_override]} )) &&" ) echo "${verbosity}: \$#";
$( (( $level > ${config[verb_override]} )) && echo "exit $(( level - ${config[verb_override]} ));" )
}
MACRO
eval "$macro"
(( level++ ))
done
}
# INITIALIZATION SECTION
_messages
After initialization, anywhere in your code you can use things like:
! (( $# )) && _error "parameter expected"
[[ -f somefile ]] && _warning "file $somefile already exists"
_info "some info"
_status "running command"
if (( ${config[verbosity]} <= 1 )); then
command
else
command -v
fi
# explicitly changing verbosity at run time
old_verbosity=${config[verbosity]}
config[verbosity]=1
etc.
verbose=false
while getopts "v" OPTION
do
case $OPTION in
v) verbose=true
;;
esac
done
Then
$verbose && echo "Verbose mode on" || echo "Verbose mode off"
This will execute /bin/true or /bin/false, returning 0 or 1 respectively.
To avoid using multiple if statements or using a variable to hold a function name how about declaring different functions based on the verbosity!
This works for ALL bourne shell derivatives not just bash!
#verbose=verbose_true # uncomment to make script verbose
if [ "$verbose" ]; then
log() { echo "$#"; }
else
log() { :; }
fi
log This Script is Verbose
NOTE: the use of "verbose=verbose_true" makes script tracing a lot nicer
but you can make that one if however you like.
I would propose a modified version of #fentas's answer:
# set verbose level to info
__VERBOSE=6
declare -A LOG_LEVELS
# https://en.wikipedia.org/wiki/Syslog#Severity_level
LOG_LEVELS=([0]="emerg" [1]="alert" [2]="crit" [3]="err" [4]="warning" [5]="notice" [6]="info" [7]="debug")
function .log () {
local LEVEL=${1}
shift
if [ ${__VERBOSE} -ge ${LEVEL} ]; then
if [ -t 0 ]; then
# seems we are in an interactive shell
echo "[${LOG_LEVELS[$LEVEL]}]" "$#" >&2
else
# seems we are in a cron job
logger -p "${LOG_LEVELS[$LEVEL]}" -t "$0[$$]" -- "$*"
fi
fi
}

Resources