bash: command line arguments as script triggers - bash

In my bash routine I am using two alternative methods to use arguments defined during the script execution and assign them to specific variables used by some other functions within the same script:
# tested and works OK
parse_args() {
MY_SORT_METHOD=1 # sort strings according the number of the ligand
MY_FILT_METHOD=0 # filt data according to all strategies
while (($#)); do
case "$1" in
--sort=*)
IFS== read -r _ val <<<"$1"
case "$val" in 1|2) ;; *)
echo "Wrong value is set: please select either the 1st or 2nd method of sorting"
return 1
esac
MY_SORT_METHOD="$val"
;;
--filt=*)
IFS== read -r _ val <<<"$1"
case "$val" in [0-4]) ;; *)
echo "Wrong value is chosen: please select one of four strategies of filtering (--filt=1,2,3,4). Othervise select all four strategies (--filt=0)"
return 1
esac
MY_FILT_METHOD="$val"
;;
esac
shift
done
}
# this does not work
parse_args2() {
args=$(getopt -o '' -l sort:,filt: -- "$#")
eval "set -- $args"
MY_SORT_METHOD=1
MY_FILT_METHOD=0
set -x
while (($#)); do
case "$1" in
--sort) MY_SORT_METHOD="$2"; shift; ;;
--filt) MY_FILT_METHOD="$2"; shift; ;;
--) shift; break; ;;
esac
shift;
done
}
While parse_args() is working OK, there is some bug in parse_args2(), which does not allow to execute this function in the simular fashion... What should I fix there, assuming that the both functions are called in the bash script just using using
parse_args $*
Also here is another function, which uses 4 different AWK scripts to process the same CSV file in the case if a variable defined in the argument is 0: "${MY_FILT_METHOD}" = "0":
filter_csv () {
for d in "${storage}"/*/; do
dir_name=${d%*/}
dir_name=$(basename "$dir_name")
[ "${MY_FILT_METHOD}" = "0" ] && awk -v min_lines=3 -F ", " 'a < $2 {for(idx=0; idx < i; idx++) {print arr[idx]} print $0; a=int($2); i=0; printed=NR} a > $2 && NR > 1 {arr[i]=$0; i++}END{if(printed <= min_lines) {for(idx = 0; idx <= min_lines - printed; idx++){print arr[idx]}}}' "${d}"/${dir_name}*_proc.csv > "${d}"/${dir_name}_str1.csv
[ "${MY_FILT_METHOD}" = "0" ] && awk -v cut1="$cut1" -v cut2="$cut2" -F ', ' 'NR == 1 {next} FNR==NR {if (max < $2) {max=$2; n=FNR+1} next} FNR <= 2 || (FNR == n && $2 > (cut1 * max)) || $2 > (cut2 * max)' "${d}"/${dir_name}*_proc.csv{,} > "${d}"/${dir_name}_str2.csv
[ "${MY_FILT_METHOD}" = "0" ] && awk -v cut3="$cut3" -v cut4="$cut4" -F ', ' 'NR == 1 {next} FNR==NR {if (maxP < $2) maxP=$2; if (minD=="" || minD > $3) minD=$3; next} FNR <= 2 || ($2 >= (cut3 * maxP) && $3 <= (cut4 * minD))' "${d}"/${dir_name}*_proc.csv{,} > "${d}"/${dir_name}_str3a.csv
[ "${MY_FILT_METHOD}" = "0" ] && awk -v cut3="$cut3" -v cut4="$cut4" -F ', ' 'NR == 1 {next} FNR==NR {if (maxP < $2) maxP=$2; if (minD=="" || minD > $3) minD=$3; next} FNR==2{p=$0} FNR == 1 || ($2 >= (cut3 * maxP) && $3 <= (cut4 * minD)) {++c; print} END {if (c==1) print p}' "${d}"/${dir_name}*_proc.csv{,} > "${d}"/${dir_name}_str3b.csv
done
Would it be possible rather to merge those 4 awk commands into one block activated by [ "${MY_FILT_METHOD}" = "0" ] && ?

Related

False positive IF condition, in BASH. Testing ((A ||B ) && C)

Please see below example test code that I am getting false positive for:
#!/bin/bash
sub1(){
if [[ (($1 -lt 0) || "$1" == "-0") && ($3 =~ re) ]]
then
echo "Sub1: Condition is true $1 $2 $3 $4"
else
echo "Sub1: Condition is false $1 $2 $3 $4"
fi
}
sub2(){
if [[ (($1 -lt 0) || ("$1" == "-0")) && ($3 -ge 0) ]]
then
echo "Sub2: Condition is true $1 $2 $3 $4"
else
echo "Sub2: Condition is false $1 $2 $3 $4"
fi
}
nz='^-0$'
re='^[1-9]+$'
ne='^-[1-9]+$'
A=-0
B=50
C=-0
D=55
sub1 "$A" "$B" "$C" "$D"
sub2 "$A" "$B" "$C" "$D"
I am trying to pass multiple values of C (ex. 0, -0, 1, -1 etc)
Function Sub1 I am trying to check conditions using regex.
Function Sub2 I am trying to check conditions using arithmetic and string conditions.
None of them gives error but both provides false positive when you test with all possible values of C.
Any idea what's wrong in If statement?

get specific part of a string and set the another part to null

hope you are doing fine
i want to get a substring of my string which has a format of : 18d6m2s and so.. all i'm interested in is getting only the number of days and if the string contains only minutes/seconds i want to get a value of null/zero ..
examples:
age=12d23m2s ... the needed output is 12
age=21m... the needed output is 0
age=22s... the needed output is 0
my solution was to use cut (i can't have a separate bash script and this would be the input for another command..)..
my solution:
awk -F "d" '{print $1}' $6
but this is only valid for strings with the d characters.. i want to set the strings which contains m/s only to zero/null..
how can i do that ?
We can use the NF var to check if the delimiter (d) has been found, and print accordingly:
#!/bin/bash
awk -F "d" '{print (NF > 1) ? $1 : 0}' <<< '12d23m2s'
awk -F "d" '{print (NF > 1) ? $1 : 0}' <<< '21m'
awk -F "d" '{print (NF > 1) ? $1 : 0}' <<< '22'
12
0
0
Try it online!
You can use parameter expansion:
for age in 12d23m2s 12d 12d1s 21m1s 22s ; do
days=${age%d*} # Remove everything starting from "d".
if [[ $days == *[ms]* ]] ; then
days=0
fi
echo $days
done
You could use a regular expression:
if [[ $age =~ ([0-9]+)d ]]; then
echo "${BASH_REMATCH[1]}"
fi
Encapsulated into a function:
get_days() {
[[ $1 =~ ([0-9]+)d ]] && echo "${BASH_REMATCH[1]}" || echo 0
}
for age in 12d23m2s 21m 22s; do
days=$(get_days "$age")
echo "days in $age => $days"
done
days in 12d23m2s => 12
days in 21m => 0
days in 22s => 0
Using perl one-liner, with positive lookaround to dig between = and d:
$ perl -ne '(/(?<==)(\d+)(?=d)/ && print "$1\n") || print "0\n";' file
Output:
12
0
0

How to compare decimal values in shell script, e.g., 12.2.0.13 to 12.2.0.14

I have a requirement for comparing the values with 4 decimal points. I tried with bc, but it didn't work. How can I do this?
amt="12.2.0.13" opn_amt="12.2.0.14"
if [ $(bc <<< "$amt <= $opn_amt") -eq 1 ]; then
echo "12.2.0.13"
else
echo "12.2.0.14"
fi
Please try below code;
To output larger IP:
amt="12.2.0.13";opn_amt="12.1.0.14";C=$opn_amt; for v in 1 2 3 4; do A=$(echo $amt | cut -d '.' -f$v); B=$(echo $opn_amt | cut -d '.' -f$v); if [ $A -gt $B ]; then C=$amt; break; fi; done; echo $C
To output less IP:
amt="12.1.0.13";opn_amt="12.1.0.14";C=$opn_amt; for v in 1 2 3 4; do A=$(echo $amt | cut -d '.' -f$v); B=$(echo $opn_amt | cut -d '.' -f$v); if [ $A -lt $B ]; then C=$amt; break; fi; done; echo $C
To do something based on conditon:
$ amt="12.2.0.14";opn_amt="12.1.0.14";C=0; for v in 1 2 3 4; do A=$(echo $amt | cut -d '.' -f$v); B=$(echo $opn_amt | cut -d '.' -f$v);if [ $A -lt $B ]; then C=1; break; fi; done
$ if [ $C -eq 0 ]
> then
> echo "amt is great or equal then opn_amt"
> else
> echo "amt is less than opn_amt"
> fi
amt is great or equal then opn_amt
If the number of digits between each . is fixed and same in both strings, you can compare by removing the . from the variables. So they will be considered integer before comparing.
sh-4.4$ amt="12.2.0.13"
sh-4.4$ open_amt="12.2.0.14"
sh-4.4$ [ "${amt//./}" -gt "${open_amt//./}" ] && echo "$amt" || echo "$open_amt"
12.2.0.14
sh-4.4$
Here's a comparator implementation for ksh. Adapting it to other shells is left as an exercise for the reader.
function get_version_component {
typeset -r version="$1"
typeset -ri index="$2"
typeset value=$(print -- "$version" | cut -d. -f$index -s)
[[ "$value" == "" ]] && value=0
[[ "$value" == +([0-9]) ]] || return 1
print $value
return 0
}
# Compare two version numbers, up to 20 levels deep.
# For comparison purposes, missing values are assumed to be zero (1.2.3.0 == 1.2.3).
# Output -1 on first < second, 0 if they are equal, 1 if first > second.
# (($(compare_versions 10.3.59.37 10.3.59) > 0)) && echo pass1
# Returns 0 on success, non-0 on invalid version number.
function compare_versions {
[[ -z "$1" || -z "$2" ]] && return 1
typeset -r first="${1}.0" second="${2}.0"
typeset -i index=0 n1 n2
for (( index = 1 ; index < 20 ; index++ ))
do
n1=$(get_version_component "$first" $index) || return 1
n2=$(get_version_component "$second" $index) || return 1
if ((n1 < n2))
then
print -- "-1"
return 0
elif ((n1 > n2))
then
print "1"
return 0
fi
done
print "0"
return 0
}
# # Test cases
# # Equal
# compare_versions 10.3.59.37 10.3.59.37 || print errored
# compare_versions 10.3.59.0 10.3.59 || print errored
# compare_versions 10.3.59 10.3.59.0 || print errored
#
# # Less
# compare_versions 9.2.59.37 10.3.59.37 || print errored
# compare_versions 10.2.59.37 10.3.59.37 || print errored
# compare_versions 10.3.59.37 10.3.59.39 || print errored
# compare_versions 10.3.59.37 10.3.60 || print errored
# compare_versions 10.3.59 10.3.59.37 || print errored
#
# # Greater
# compare_versions 10.2.59.37 9.3.59.37 || print errored
# compare_versions 10.3.59.37 10.2.59.37 || print errored
# compare_versions 10.3.59.39 10.3.59.37 || print errored
# compare_versions 10.3.60 10.3.59.37 || print errored
# compare_versions 10.3.59.37 10.3.59 || print errored
#
# # Errors
# compare_versions 10.x.59.37 10.3.59.37 && print "Error didn't 1"
# compare_versions 10.3.59.37 "" && print "Error didn't 2"
# compare_versions "" 9.3.59.37 && print "Error didn't 3"
#

how can i mix or and and in an if statement in bash?

i have this function that accepts 3 parameters , ech one contain of 4 numbers and a capital letter for example : "1234A"
and i want to print 1 if the second parameter is bigger than the third one and smaller than the first one ,
i wrote this function that i cutted the 4 numbers in a parameter for each parameter and the letter in diffrent paramater for each one and i began to compare
but the problem it print nothing !!
anyone know how to do things in one if statement rather than two if statements ??
what i did :
function check {
curr_letter=`echo "$1" | cut -c5`
min_letter=`echo "$3" | cut -c5`
sm_letter=`echo "$2" | cut -c5`
curr_nums=`echo "$1" | cut -c1-4`
min_nums=`echo "$3" | cut -c1-4`
sm_nums=`echo "$2" | cut -c1-4`
if [[ sm_nums -eq curr_nums && sm_letter < curr_letter ]] ; then
if [[ sm_nums -eq min_nums && sm_letter > min_letter ]] ; then
echo 1
fi
if [[ sm_nums > min_nums ]] ; then
echo 1
fi
fi
if [[ sm_nums < curr_nums ]] ; then
if [[ sm_nums -eq min_nums && sm_letter > min_letter ]] ; then
echo 1
fi
if [[ sm_nums > min_nums ]] ; then
echo 1
fi
fi
}
i get nothing when i test this in bash , i get an empty line..
this is how i tested it :
p=`check "1617B" "1617A" "0000A"` echo $p
You can omit the $ in variable names within arithmetic context ((...)).
Within [[ ... ]] you cannot omit it.
Instead of calling echo ... | cut -c..., you can easily extract substrings using Bash's very own syntax {var:start:length}.
Within a [[ ... ]] or ((...)),
use == instead of -eq.
Note however that < and > operators sort lexicographically within a [[ ... ]], but numerically in arithmetic context ((...)).
Therefore the string-valued variables (named *_letter in your example)
should be compared within [[ ... ]], the numeric variables (named *_nums in your example) should be compared within ((...)).
Like this:
function check() {
curr_letter=${1:4:1}
min_letter=${3:4:1}
sm_letter=${2:4:1}
curr_nums=${1:0:4}
min_nums=${3:0:4}
sm_nums=${2:0:4}
if (( sm_nums == curr_nums )) && [[ $sm_letter < $curr_letter ]]; then
if (( sm_nums == min_nums )) && [[ $sm_letter > $min_letter ]] ; then
echo 1
fi
if (( sm_nums > min_nums )) ; then
echo 1
fi
fi
if (( sm_nums < curr_nums )) ; then
if (( sm_nums == min_nums )) && [[ $sm_letter > $min_letter ]] ; then
echo 1
fi
if (( sm_nums > min_nums )) ; then
echo 1
fi
fi
}
Lastly, instead of p=`check "1617B" "1617A" "0000A"`; echo $p,
better write like this:
echo $(check "1617B" "1617A" "0000A")
why not just
awk '$3 <= $2 && $2 <= $1 {print 1}'
or if you need a function
check() { awk '$3 <= $2 && $2 <= $1 {print 1}' <<< "$#"; }
or
check() { awk "BEGIN{if($3 <= $2 && $2 <= $1) print 1}"; }

IF Condition for intervals made by real numbers in bash

This is a bash routine to compare two numbers with some defined intervals given by integers numbers:
#!/bin/bash
# The comparing function
function compareInterval {
t1=$1
t2=$2
shift 2
while (( "$2" )); do
if (( $t1 >= $1 && $t2 <= $2 )); then
# got match
return 0
fi
shift 2
done
return 1
}
# sample values
t_initial=2
t_final=4
# Invocation. Compares against 1-3, 3-5, 2-5
if compareInterval $t_initial $t_final 1 3 3 5 2 5; then
echo Got match
fi
If the intervals are given by real numbers, i.e., 1.234, how does the condition in the function change?
Here's a new version of the code:
#!/bin/bash
function compareInterval {
t1=$1
t2=$2
shift 2
while (( $(awk -v var="$2" 'BEGIN{ if (var=="") print 0; else print 1; }') )); do
var1=$(awk -v t1="$t1" -v t2="$1" 'BEGIN{ print (t1 >= t2) }')
var2=$(awk -v t3="$t2" -v t4="$2" 'BEGIN{ print (t3 <= t4) }')
if [[ "$var1" -eq "1" && "$var2" -eq "1" ]]; then
# got match
return 0
fi
shift 2
done
return 1
}
t_initial=4399.75148230007220954256
t_final=4399.75172111932808454256
if compareInterval $t_initial $t_final 4399.48390124308 4400.47652912846 3 5 2 5; then
echo Got match
fi
Just another pure bash solution:
#!/bin/bash
function compareInterval {
t1=$1 t2=$2
shift 2
while [[ $# -ge 2 ]]; do
is_ge "$t1" "$1" && is_le "$t2" "$2" && return 0 ## Got match.
shift 2
done
return 1
}
function is_ge {
local A1 A2 B1 B2
if [[ $1 == *.* ]]; then
A1=${1%%.*}
A2=${1##*.}
else
A1=$1
A2=0
fi
if [[ $2 == *.* ]]; then
B1=${2%%.*}
B2=${2##*.}
else
B1=$2
B2=0
fi
(( L = ${#A2} > ${#B2} ? ${#A2} : ${#B2} ))
A2=$A2'00000000000000000000'; A2=1${A2:0:L}
B2=$B2'00000000000000000000'; B2=1${B2:0:L}
(( A1 == B1 ? A2 >= B2 : A1 > B1 ))
}
function is_le {
local A1 A2 B1 B2
if [[ $1 == *.* ]]; then
A1=${1%%.*}
A2=${1##*.}
else
A1=$1
A2=0
fi
if [[ $2 == *.* ]]; then
B1=${2%%.*}
B2=${2##*.}
else
B1=$2
B2=0
fi
(( L = ${#A2} > ${#B2} ? ${#A2} : ${#B2} ))
A2=$A2'00000000000000000000'; A2=1${A2:0:L}
B2=$B2'00000000000000000000'; B2=1${B2:0:L}
(( A1 == B1 ? A2 <= B2 : A1 < B1 ))
}
t_initial=2.4
t_final=4.5
if compareInterval "$t_initial" "$t_final" 1 3 3 5 2 5; then
echo 'Got match.'
fi
Note: Of course sanity checks can be added but I find that not too necessary for now.

Resources