Build a bash command with conditional parameters and cuote parameters - bash

I need to build a bash command in a script depending on some cuote or normal parameters. For example:
BAYES)
class="weka.classifiers.bayes.BayesNet"
A="-D -Q weka.classifiers.bayes.net.search.local.K2 -- -P 1 -S BAYES -E"
B="weka.classifiers.bayes.net.estimate.SimpleEstimator -- -A 0.5" ;;
LOGISTIC)
class="weka.classifiers.functions.Logistic"
A="-R 1.0E-8 -M -1 -num-decimal-places 4" ;;
SIMPLELOG)
class="weka.classifiers.functions.SimpleLogistic"
A="-I 0 -M 500 -H 50 -W 0.0" ;;
SMO)
class="weka.classifiers.functions.SMO"
A="-C 1.0 -L 0.001 -P 1.0E-12 -N 0 -V -1 -W 1 -K"
A1="weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0" ;;
IBK)
class="weka.classifiers.lazy.IBk"
A="-K 1 -W 0 -A "
A1="weka.core.neighboursearch.LinearNNSearch -A"
A2="weka.core.EuclideanDistance -R first-last" ;;
KSTAR)
class="weka.classifiers.lazy.KStar"
A="-B 20 -M a" ;;
...
java -Xmx"$mem"m -cp "$WEKA_INSTALL_DIR/weka.jar" $class -s $i -t "$file" $A "$A1" $B "$B1"
However, my problem is that in some conditions, when $A1 is empty, the "$A1" parameter is not valid. The same with "$B1". And the parameter could be in any combination ($A1 with $B1, $A1 without $B2, ...).
Also I've tried include $A1 in $A as following:
A="-C 1.0 -L 0.001 -P 1.0E-12 -N 0 -V -1 -W 1 -K \"weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0\""
and execute:
java -Xmx"$mem"m -cp "$WEKA_INSTALL_DIR/weka.jar" $class -s $i -t "$file" $A
but this doesn't work.

You cannot safely and reliably store multiple arguments in a single string; you need to use arrays; this is their intended use case. Make sure to initialize any arrays that won't be used, so that they "disappear" when expanded.
# If A is undefined, "${A[#]}" is an empty string.
# But if A=(), then "${A[#]}" simply disappears from the command line.
A=()
B=()
A1=()
A2=()
case $something in
BAYES)
class="weka.classifiers.bayes.BayesNet"
A=(-D -Q weka.classifiers.bayes.net.search.local.K2 -- -P 1 -S BAYES -E)
B=(weka.classifiers.bayes.net.estimate.SimpleEstimator -- -A 0.5);;
LOGISTIC)
class="weka.classifiers.functions.Logistic"
A=(-R 1.0E-8 -M -1 -num-decimal-places 4);;
SIMPLELOG)
class="weka.classifiers.functions.SimpleLogistic"
A=(-I 0 -M 500 -H 50 -W 0.0) ;;
SMO)
class="weka.classifiers.functions.SMO"
A=(-C 1.0 -L 0.001 -P 1.0E-12 -N 0 -V -1 -W 1 -K)
A1=(weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0) ;;
IBK)
class="weka.classifiers.lazy.IBk"
A=(-K 1 -W 0 -A)
A1=(weka.core.neighboursearch.LinearNNSearch -A)
A2=(weka.core.EuclideanDistance -R first-last);;
KSTAR)
class="weka.classifiers.lazy.KStar"
A=(-B 20 -M a) ;;
esac
and always quote parameter expansions.
java -Xmx"$mem"m -cp "$WEKA_INSTALL_DIR/weka.jar" \
"$class" -s "$i" -t "$file" "${A[#]}" "${A1[#]}" "${B[#]}" "${B1[#]}"

SOLUTION:
I solved all my problems using only a parameter A like this:
BAYES)
class="weka.classifiers.bayes.BayesNet"
A=(-D -Q weka.classifiers.bayes.net.search.local.K2 -- -P 1 -S BAYES -E weka.classifiers.bayes.net.estimate.SimpleEstimator -- -A 0.5);;
SMO)
class="weka.classifiers.functions.SMO"
A=(-C 1.0 -L 0.001 -P 1.0E-12 -N 0 -V -1 -W 1 -K "weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0");;
java -Xmx"$mem"m -cp "$WEKA_INSTALL_DIR/weka.jar" $class -s $i -t "$file" "${A[#]}"

From your question, I did:
Initialized the variables
Completed the case statement
Removed some not required double quotes
Defined some variables for which you did not provide values for
backslash your double quotes if you must have then in the java command
If you need double quotes for certain variables, put these in the variables. This way you will not have "" in your java command if the variables is empty. I did this for A1 in case IBK.
This will get you started, modify as required:
#!/bin/bash
#
mem="512"
WEKA_INSTALL_DIR='/opt/weka'
class=""
i="value-of-i"
A=""
A1=""
B=""
B1=""
file="SOMEFILE"
case $1 in
'BAYES')
class="weka.classifiers.bayes.BayesNet"
A="-D -Q weka.classifiers.bayes.net.search.local.K2 -- -P 1 -S BAYES -E"
B="weka.classifiers.bayes.net.estimate.SimpleEstimator -- -A 0.5"
;;
'LOGISTIC')
class="weka.classifiers.functions.Logistic"
A="-R 1.0E-8 -M -1 -num-decimal-places 4"
;;
'SIMPLELOG')
class="weka.classifiers.functions.SimpleLogistic"
A="-I 0 -M 500 -H 50 -W 0.0"
;;
'SMO')
class="weka.classifiers.functions.SMO"
A="-C 1.0 -L 0.001 -P 1.0E-12 -N 0 -V -1 -W 1 -K"
A1="weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0"
;;
'IBK')
class="weka.classifiers.lazy.IBk"
A="-K 1 -W 0 -A "
A1="\"weka.core.neighboursearch.LinearNNSearch -A\""
A2="weka.core.EuclideanDistance -R first-last"
;;
'KSTAR')
class="weka.classifiers.lazy.KStar"
A="-B 20 -M a"
;;
*)
# default options
;;
esac
echo java -Xmx${mem}m -cp $WEKA_INSTALL_DIR/weka.jar $class -s $i -t $file $A $A1 $B $B1
Example:
./test.bash LOGISTIC
java -Xmx512m -cp /opt/weka/weka.jar weka.classifiers.functions.Logistic -s value-of-i -t SOMEFILE -R 1.0E-8 -M -1 -num-decimal-places 4
./test.bash IBK
java -Xmx512m -cp /opt/weka/weka.jar weka.classifiers.lazy.IBk -s value-of-i -t SOMEFILE -K 1 -W 0 -A "weka.core.neighboursearch.LinearNNSearch -A"

Related

Hide or suppress arguments value passed to a shell script

From a local machine I am running a shell script on a remote server and passing some arguments to the scripts. Like test.sh "name" "age"
This is my script:
#!/bin/bash
echo $1
echo $2
On the remote server while the script is executing and if I run ps aux | grep .sh i could see the value of the two parameters. Like bash -s name age
Is there a way to suppress or hide the values in the running shell process so that one can see the parameters ?
I have an idea. You could create a global environment variable with unique name and save there the positional arguments, then re-exec your process and get the arguments:
#!/bin/bash
if [[ -z "$MYARGS" ]]; then
export MYARGS="$(printf "%q " "$#")"
exec "$0"
fi
eval set -- "$MYARGS"
printf -- "My arguments:\n"
printf -- "-- %s\n" "$#"
sleep infinity
It will hide it from ps aux:
$ ps aux | grep 1.sh
kamil 196704 0.4 0.0 9768 2084 pts/1 S+ 16:49 0:00 /bin/bash /tmp/1.sh
kamil 196777 0.0 0.0 8924 1640 pts/2 S+ 16:49 0:00 grep 1.sh
The environment variable could be still extracted from /proc:
$ cat /proc/196704/environ | sed -z '/MYARGS/!d'; echo
MYARGS=1 2 3 54 5
Another way might be writing the positional arguments as a string on stdin and pass it to outselves with original input:
#!/bin/bash
if [[ -z "$MYARGS" ]]; then
export MYARGS=1 # just so it's set
# restart outselves with no arguments
exec "$0" < <(
# Stream arguments on stdin on one line
printf "%q " "$#" | xxd -p | tr -d '\n'
echo
exec cat
)
fi
IFS= read -r args # read _one line_ of input - it's our arguments
args=$(xxd -r -p <<<"$args") # encoded with xxd
eval set -- "$args"
printf -- "My arguments:\n"
printf -- "-- %s\n" "$#"
sleep infinity
Here's a way to take the cmd line args and read args from stdin:
#!/usr/bin/env bash
args=()
for arg; do
printf "%d\t%s\n" $((++c)) "$arg"
args+=("$arg")
done
if ! [[ -t 0 ]]; then
while IFS= read -r arg; do
args+=("$arg")
done
fi
declare -p args
Do you can do:
script.sh hello world
printf "%s\n" hello world | script.sh
echo world | script.sh hello

Pass arguments to a script that is an argument to a different script

I am new to programming, so plz bear with the way I try to explain my problem (also any help regarding how to elegantly phrase the tile is welcome).
I have a bash script (say for example script1.sh ) that takes in arguments a, b and c(another script). Essentially, argument c for script1.sh is the name of another script (let's say script2.sh). However, script2.sh takes in arguments d,e and f. So my question is, how do I pass arguments to script1.sh ?? (example, ./script1.sh -a 1 -b 2 -c script2.sh -d 3 -e 4 -f 5)
Sorry in advance if the above does not make sense, not sure how else to phrase it...
You should use "" for that
./script1.sh -a 1 -b 2 -c "script2.sh -d 3 -e 4 -f 5"
Try script1.sh with this code
#!/bin/bash
for arg in "$#"; { # loop through all arguments passed to the script
echo $arg
}
The output will be
$ ./script1.sh -a 1 -b 2 -c "script2.sh -d 3 -e 4 -f 5"
-a
1
-b
2
-c
script2.sh -d 3 -e 4 -f 5
But if you run this
#!/bin/bash
for arg in $#; { # no double quotes around $#
echo $arg
}
The output will be
$ ./script1.sh -a 1 -b 2 -c "script2.sh -d 3 -e 4 -f 5"
-a
1
-b
2
-c
script2.sh
-d
3
4
-f
5
But there is no -e why? Coz echo supports argument -e and use it.

How to process multiple command line arguments in Bash? [duplicate]

This question already has answers here:
How to iterate over arguments in a Bash script
(9 answers)
How do I parse command line arguments in Bash?
(40 answers)
Closed 5 years ago.
I am having problem allowing my script to take more than three arguments. My script will take commands like this, for example:
./myscript.sh -i -v -r filename
so far if it only takes two arguments plus filename:
./myscript.sh -i -v filename
If I run the full commands, [-i] [-v] [-r] it gives this errors...
"mv: invalid option -- 'r'
Try 'mv --help' for more information."
here is my code so far....
#!/bin/bash
dirfile='.trash'
verbose=
function helpfunction()
{
echo "=============== HELP COMMAND ============="
echo ""
echo "-h | --help"
echo "-i | --interactive"
echo "-v | --verbose"
echo "-r | --recursive"
echo ""
}
if [ $# -eq 0 ]; then
echo "no commands"
exit 1
fi
option="${1}"
case ${option} in
-h | --help)
helpfunction
exit
;;
#interactive -i or --interactive
-i) FILE="${*}"
echo "File name is $FILE"
echo -n "Do you want to remove this file (y/n)? "
read answer
if echo "$answer" | grep -iq "^y" ;then
mv $FILE $dirfile
fi
;;
#verbose -v or --verbose
-v) FILE="${*}"
if [ -f "$FILE" ]; then
-v $FILE
fi
;;
#recursive -r or --recursive
-r) FILE="${*}"
if [ -d "${*}" ]; then
rm -r "$FILE"
fi
;;
#unremove -u or --unremove
-u) FILE="${*}"
for file in $dirfile*;
do
if [[ -f $file ]]; then
echo "file found"
else
echo "File not found"
fi
done
;;
#list -l or --list
-l) FILE="${*}"
for entry in "$dirfile"/*
do
echo "$entry"
done
;;
*)
echo "`basename ${0}`:usage: [-f file] | [-d directory]"
exit 1 # Command to come out of the program with status 1
;;
esac
When you say
FILE="${*}"
You're taking all the remaining arguments as one string, and assigning that to FILE. You can see this by adding 'set -x' to the top of your script:
opus$ ./myscript.sh -i -v -r filename
+ '[' 4 -eq 0 ']'
+ option=-i
+ case ${option} in
+ FILE='-i -v -r filename'
+ echo 'File name is -i -v -r filename'
File name is -i -v -r filename
+ echo -n 'Do you want to remove this file (y/n)? '
Do you want to remove this file (y/n)? + read answer
y
+ grep -iq '^y'
+ echo y
+ mv -i -v -r filename
mv: invalid option -- 'r'
Try 'mv --help' for more information.
Saying $1 gives the first argument. After you discover that, you can use shift to load the next argument as $1, and cycle through them like that.
Alternatively, you could just use the builtin getops argument parser.
Joe
You are getting error at this line
mv $FILE $dirfile
Here is the execution of your script in debug mode.
bash -x ./myscript.sh -i -v -r test.txt
+ dirfile=.trash
+ verbose=
+ '[' 4 -eq 0 ']'
+ option=-i
+ case ${option} in
+ FILE='-i -v -r test.txt'
+ echo 'File name is -i -v -r test.txt'
File name is -i -v -r test.txt
+ echo -n 'Do you want to remove this file (y/n)? '
Do you want to remove this file (y/n)? + read answer
y
+ echo y
+ grep -iq '^y'
+ mv -i -v -r test.txt .trash
mv: invalid option -- 'r'
Try 'mv --help' for more information.
-r is not a valid argument for mv.
Update your script like FILE=$4
if you want -i -v -r as optional, you can extract the last argument following this Getting the last argument passed to a shell script
You can also use ${!#} for getting the last parameter.

reading from serial using shellscript

I have a serial port device that I would like to test using Linux command line.
And if I run the following command from terminal, it gives output
cat < /dev/ttyS0 &
This command opens the serial port and relays what it reads from it to its stdout.So, I tried it from shell script file but it is not working
fName="test.txt";
awk '
BEGIN { RS = "" ; FS = "\n" }
{
address = '/dev/ttyS0';
system("cat < " address );
}
END {
}' "$fName";
But it is not working and giving output.How can I listen to communication between a process and a serial port? Thanks
Using awk timeouts
I've successfully read something under dash, be using GAWK_READ_TIMEOUT environment variable:
out=`GAWK_READ_TIMEOUT=3000 awk '{print}' </dev/ttyS0 & sleep 1 ; echo foo >/dev/ttyS0`
On my terminal, this output:
echo "$out"
foo
Password:
or
echo "$out"
Login incorrect
testhost login:
Using bash timeouts
You could use FD under bash as:
exec 5>/dev/ttyS0
exec 6</dev/ttyS0
while read -t .1 -u 6 line;do
echo $line
done
or, to read unfinished lines:
while IFS= read -d '' -t .1 -u 6 -rn 1 char;do
echo -n "$char"
done
echo
So you could:
echo 'root' >&5
while IFS= read -d '' -t .1 -u 6 -rn 1 char;do
echo -n "$char"
done
echo 'password is 1234' >&5
while IFS= read -d '' -t .1 -u 6 -rn 1 char;do
echo -n "$char"
done
... Once done, you could close FD by running:
exec 6<&-
exec 5>&-
Sample bash poor terminal script
I've logged and test some commands with:
#!/bin/bash
exec 5>/dev/ttyS0
exec 6</dev/ttyS0
readbuf() {
while IFS= read -d '' -t .1 -u 6 -rn 1 char;do
echo -n "$char"
done
};
while [ "$cmd" != "tquit" ] ;do
readbuf
read cmd
echo >&5 "$cmd"
done

Bash: execute command repeatedly and write output tab seperated to file

I want my file to Log like that:
Time_Namelookup: 0,1 0,2 0,12 0,45 ...
Time_Connect: 0,34 0,23 0,23 0,11 ...
Time_Starttransfer: 0,9 0,23 0,12 ...
I want that Values are added to their specific line every n seconds.
I got a code like this:
while [ "true" ]
do
echo "Time_Namelookup:" >> $file
curl -w "%{time_namelookup}\t" -o /dev/null -s https://website.com/
echo "Time_connect" >> $file
curl -w "%{time_connect}\t" -o /dev/null -s https://website.com/
echo "Time_Starttransfer:" >> $file
curl -w "%{time_starttransfer}\t" -o /dev/null -s https://website.com/
sleep 5
done
But I get something like
Time_Namelookup: 0,1
Time_Connect: 0,34
Time_Starttransfer: 0,9
Time_Namelookup: 0,2
Time_Connect:0,23 0,23
Time_Starttransfer: 0,23
Time_Namelookup: 0,45
Time_Connect: 0,11
Time_Starttransfer: 0,12
Can you help me ?
You could put this inside your loop
if [ ! -f $file ]; then
echo "Time_Namelookup:" > $file
echo "Time_Connect:" >> $file
echo "Time_Starttransfer:" >> $file
fi
name_lookup=$(curl -w "%{time_namelookup}\t" -o /dev/null -s https://website.com/)
connect=$(curl -w "%{time_connect}\t" -o /dev/null -s https://website.com/)
starttransfer=$(curl -w "%{time_starttransfer}\t" -o /dev/null -s https://website.com/)
sed -i -e "s/\(Time_Starttransfer:.*\)/\1 $starttransfer/" \
-e "s/\(Time_Connect:.*\)/\1 $connect/" \
-e "s/\(Time_Starttransfer:.*\)/\1 $starttransfer/" \
$file
you can try this;
file=yourFile
echo "Time_Namelookup :" >> $file
echo "Time_Connect :" >> $file
echo "Time_Starttransfer:" >> $file
while [ "true" ]
do
time_namelookup=$(curl -w "%{time_namelookup}\t" -o /dev/null -s https://website.com/)
time_connect=$(curl -w "%{time_connect}\t" -o /dev/null -s https://website.com/)
time_starttransfer=$(curl -w "%{time_starttransfer}\t" -o /dev/null -s https://website.com/)
sed -i "/Time_Namelookup :/s/$/\t$time_namelookup/" $file
sed -i "/Time_Connect :/s/$/\t$time_connect/" $file
sed -i "/Time_Starttransfer:/s/$/\t$time_starttransfer/" $file
sleep 5
done
sed -i : to be edited in-place.
The following part is to find "Time_Namelookup :" in the file.
/Time_Namelookup :/
/s/$/\t$time_namelookup/"
s :substitute command
/../../ :delimiter
$ :end of line character.
\t$time_namelookup: to append tab and the value.

Resources