Parsing input options containing whitespaces in a bash script - bash

I have a bash script parsing input option with a block of code like the following
for WORD in "$#" ; do
case $WORD in
--*) true ;
case $WORD in
--opt1=*)
OPT1=${WORD/--opt1=/}
shift ;;
--opt2=*)
OPT2=${WORD/--opt2=/}
shift ;;
*) echo "Unrecognized argument $WORD"
;;
esac ;;
*) echo "Option $WORD not starting with double dash."
;;
esac
done
The script is invoked by another parent program which creates the entire command line.
The output created by this parent program looks like
./childscript.sh "--opt1=value1 --opt2=value2"
The problems appear when the generated line looks like
./childscript.sh "--opt1='value11 value12' --opt2=value2"
The scripts complains saying
Option value12 not starting with double dash.
How can I modify the child bash code to make it understand white spaces inside the input options?

I don't think the generated line is what you think it is.
Your code works completely fine for me if I simply invoke it directly. With added echoes to check that the values are being stored in the right place:
$ ./child.sh --opt1='v1 v2' --opt2='v3 v4'
OPT1='v1 v2'
OPT2='v3 v4'
You should be able to confirm this. Your problem isn't in making the child script accept arguments like these, it's in having the parent script invoke it correctly.
And by the way, you don't actually want to run something like this:
./childscript.sh "--opt1=value1 --opt2=value2"
That will cause that entire string (--opt1=value1 --opt2=value2) to be read as a single argument. I suspect that you haven't told us the full story on the way the parent script is calling this. If you show us those details, we can probably help out more - or maybe this is enough of a hint.

Related

Delayed expansion of composite variable in Bash

I'm defining a variable as a composition of other variables and some text, and I'm trying to get this variable to not expand its containing variables on the assigning. But I want it to expand when called later. That way I could reuse the same template to print different results as the inner variables keep changing. I'm truing to avoid eval as much as possible as I will be receiving some of the inner variables from third parties, and I do not know what to expect.
My use case, as below, is to have some "calling stack" so I can log all messages with the same format and keep a record of the script, function, and line of the logged message in some format like this: script.sh:this_function:42.
My attempted solution
called.sh:
#!/bin/bash
SCRIPT_NAME="`basename "${BASH_SOURCE[0]}"`"
CURR_STACK="${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${CURR_STACK}"
echo
function _func_1 {
echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${CURR_STACK}"
}
_func_1
So, I intend to get the same results while printing the "${CURR_STACK}" as when printing the previous line.
If there is some built-in or other clever way to log this 'call stack', by all means, let me know! I'll gladly wave my code good-bye, but I'd still like to know how to prevent the variables from expanding right away on the assigning of CURR_STACK, but still keep them able to expand further ahead.
Am I missing some shopt?
What I've tried:
Case 1 (expanding on line 4):
CURR_STACK="${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]}"
CURR_STACK="`echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"`"
CURR_STACK="`echo "\${SCRIPT_NAME}:\${FUNCNAME[0]}:\${LINENO[0]}"`"
called.sh::7 <------------------| These are control lines
called.sh::4 <---------------. .------------| With the results I expect to get.
X
called.sh:_func_1:12 <---´ `-------| Both indicate that the values expanded
called.sh::4 <-------------------------| on line 4 - when CURR_STACK was set.
Case 2 (not expanding at all):
CURR_STACK="\${SCRIPT_NAME}:\${FUNNAME[0]}:\${LINENO[0]}"
CURR_STACK=\${SCRIPT_NAME}:\${FUNCNAME[0]}:\${LINENO[0]}
CURR_STACK="`echo '${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}'`"
called.sh::7
${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]} <-------.----| No expansion at all!...
/
called.sh::12 /
${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]} <----´
Shell variables are store plain inert text(*), not executable code; there isn't really any concept of delayed evaluation here. To make something that does something when used, create a function instead of a variable:
print_curr_stack() {
echo "$(basename "${BASH_SOURCE[1]}"):${FUNCNAME[1]}:${BASH_LINENO[0]}"
}
# ...
echo "We are now at $(print_curr_stack)"
# Or just run it directly:
print_curr_stack
Note: using BASH_SOURCE[1] and FUNCNAME[1] gets info about context the function was run from, rather than where it is in the function itself. But for some reason I'm not clear on, BASH_LINENO[1] gets the wrong info, and BASH_LINENO[0] is what you want.
You could also write it to allow the caller to specify additional text to print:
print_curr_stack() {
echo "$#" "$(basename "${BASH_SOURCE[1]}"):${FUNCNAME[1]}:${BASH_LINENO[0]}"
}
# ...
print_curr_stack "We are now at"
(* There's an exception to what I said about variables just contain inert text: some variables -- like $LINENO, $RANDOM, etc -- are handled specially by the shell itself. But you can't create new ones like this except by modifying the shell itself.)
Are you familiar with eval?
$ a=this; b=is; c=a; d=test;
$ e='echo "$a $b $c $d"';
$ eval $e;
this is a test
$ b='is NOT'; # modify one of the variables
$ eval $e;
this is NOT a test
$ f=$(eval $e); # capture the value of the "eval" statement
$ echo $f;
this is NOT a test

#( in shell case statement

I am trying to learn how a configure script in the gimp source code works by reading and trying to understand what each statement does. I am at the beginning of the file and came across a case statement to that sets posix to on. I cant undstand what the "#(" means. Is it a comment or something else?
case `(set -o) 2>/dev/null` in #(
*posix*) :
set -o posix ;; #(
*) :
;;
esac
Yes, anything that is after # in a line is considered as a comment. See this for the switch case syntax.

Loading a while true loop into a variable

I'm having a bit of trouble getting this to work/ knowing if its possible. I'm creating a game using little other than bash, this requires a lot of repeated case statements. I am trying to load all the repeated case statements into a variable, then repeat them when necessary to limit the amount of work it will take to update the shared case statements between different scripts.
Here is what I have:
#!/bin/bash
moo="[m][o][o]) echo 'thank you for following instructions' ;;"
test=$(echo "while true ; do
read -p 'type moo: ' case
case $case in
$moo
*) echo 'type moo please'
esac
done")
"$test"
The problem I run into is:
./case.sh: line 13: $'while true ; do\nread -p \'type moo: \' case\ncase in\n[m][o][o]) echo \'thank you for following instructions\' ;;\n*) echo \'type moo please\' ;;\nesac\ndone': command not found
The information in the moo variable will eventually be in a separate script and will be set by invoking it as a function within that script when I finally get a working model.
It looks like this is a workable idea, I've just reached a loss on how to invoke the variable without it acting up. If anyone has any ideas, I would greatly appreciate it.
Thank you in advance!
It doesn't work because the quotes make the variable expansion be treated as a single word.
But it wouldn't work without quotes, either, because the shell doesn't parse the output of variables for syntax like semicolon and newline. Variable expansion is done after that stage of command parsing. The only processing that's done on expanded variables is word-splitting and wildcard matching.
You need to use eval to perform all command parsing:
eval "$test"
Another problem is that the variable $case is being expanded when you assign the variable test, it's not getting the value being read by read. Since the variable doesn't have a value yet, it's being executed as:
case in ...
and this is invalid syntax. You need to escape the $ so it will be passed through literally.
There's also no need for echo, you can simply assign the string directly.
test="while true ; do
read -p 'type moo: ' case
case \$case in
$moo
*) echo 'type moo please'
esac
done"

"unbound variable" reading a boolean parameter from shell script command line

My apology for not being able to find such a seemingly trivial thing myself.
I need to pass more than one boolean parameter to shell script (Bash) as follows:
./script --parameter1 --parameter2
and so on.
All are to be considered true if set.
In the beginning of the script, I use set -u.
Normal parameter with value passing I currently do as follows:
# this script accepts the following arguments:
# 1. mode
# 2. window
while [[ $# > 1 ]]
do
cmdline_argument="$1"
case $cmdline_argument in
-m|--mode)
mode="$2"
shift
;;
-w|--window)
window="$2"
shift
;;
esac
shift
done
I would like to add something like
-r|--repeat)
repeat=true
shift
;;
I do not understand why it does not work as expected.
It exits immediately with error:
./empire: line 450: repeat: unbound variable
Where the line 450 is:
if [ "$repeat" == true ];
When you use set -u, it's an error to dereference any variable that hasn't had a value explicitly assigned.
Thus, you need to set repeat=0 (or repeat=false) at the top of your script, or to use a dereference method that has an explicit default behavior when the value is unset; see BashFAQ #112.

Creating a 'yes' or 'no' menu in UNIX bash shell scripting

I'm currently writing a script, and at one point I want it check if a file already exists. If the file doesn't exist, then it should do nothing. However, if the file does exist, I want a 'y' or 'n' (yes or no) menu to appear. It should ask "Do you want to overwrite this file?".
So far I've tried writing something similar to this. Take into account that before this a function called:
therestore
exists. I want this function to occur if they type "y". Anyway, this is what I tried:
If [ -f directorypathANDfilename ] ; then
read -p "A file with the same name exists, Overwrite it? Type y/n?" yesorno
case $yesorno in
y*) therestore ;;
n*) echo "File has not been restored" ;;
esac
fi
For some reason though, the menu always pops up, even if the file DOESN'T exist and it doesn't restore it properly if I type yes! (But I know the "therestore" function works fine, because I've tested it plenty of times).
Apologies for the long-winded question. If you need any more details let me know - thanks in advance!
Does your script even run? Doesn't look like valid bash-script to me. If is not a valid keyword, but if is. Also, tests go inside angle-brackets [ ], those are not optional. Moreover you forgot the closing fi.
And another thing, it's not quite clear to me what you're testing for. Is directorypathANDfilename a variable? In that case you have to reference it with the $.
The snippet would probably work better like this:
#!/bin/bash
if [ -f "$directorypathANDfilename" ] ; then
read -p "A file with the same name exists, Overwrite it? Type y/n?" yesorno
case "$yesorno" in
y*) therestore ;;
n*) echo "File has not been restored" ;;
esac
fi

Resources