I'm getting dollar values from a file into the variable in the form
p=$1234.56, and would like remove $ and decimal places to get integer value in my conditional like
if [[ ${p%%.*} < 1000]]; then p=${p}0; fi
But this doesn't remove the $ sign, and I don't want to do it in 2 steps and modify actual variable, as I need $ for later use.
How to get integer value regardless of number of digits ie ($2.2, $123456.1234...)?
Unfortunately, there is no way to avoid performing multiple parameter expansions if you need to remove multiple patterns, in the general case.
In simple cases like this, you can avoid a temporary variable just by assigning back to the same variable.
p=${p#\$}
p=${p%.??}
In your specific scenario, of course, you can just replace any nonnumeric characters globally, and accept that the number will be multiplied by 100. You will obviously then need to multiply the number you compare against correspondingly.
if [[ ${p//[!0-9]/} < 100000 ]]
Of course, for this to work, you need to be sure that your variable's value conforms to your expectations. If the value could have different numbers of decimal places depending on what a user passes in or where you read the input from, you need to perform additional normalizations, or just use a different approach entirely (frequently you'd pass your input to Awk or bc which support floating point math, unlike the shell).
However, the string substitution parameter expansion ${variable//pattern/replacement} is a Bash extension, and not portable to Bourne/POSIX sh.
It's not possible without modifying the var. But you can use a subshell process with something like sed
if [[ $(sed 's/\$\([0-9]*\)\..*/\1/' <<< $p) < 1000 ]]; then p=${p}0; fi
Another option will be to use cut command to extract the substring
before the dot (if any). Then you can say something like:
p='$1234.56'
[[ $(cut -d. -f1 <<< "${p#\$}") < 1000 ]] && p=${p}0
echo "$p"
BTW the expression [[ str1 < str2 ]] performs lexicographical comparison,
meaning [[ 20 < 1000 ]] returns false because 20 sorts after
1000 in dictionary order.
If what you want to do is arithmetic comparison, you'll need to say
[[ val1 -le val2 ]] or (( val1 < val2 )) such as:
p='$1234.56'
[[ $(cut -d. -f1 <<< "${p#\$}") -le 1000 ]] && p=${p}0
echo "$p"
Related
i make a simply bash script to change number version based on the source branch of a merge request, i need increment different value if a feature or a hotfix/bigfix/fix branches names:
#!/bin/bash
if [ $# -eq 0 ]
then
echo -e "\nUsage: $0 MERGE_REQUEST_SOURCE\n"
exit 1
fi
if [ ! -f version ]; then
echo "0.0.0" > version
fi
VERSION=$(cat version)
MERGE_REQUEST_SOURCE=$1
declare -a FEATURE_LIST=("feature")
declare -a HOTFIX_LIST=("fix" "hotfix" "bugfix")
IFS="."
read -a num <<< ${VERSION}
MAJOR=${num[0]}
FEATURE=${num[1]}
HOTFIX=${num[2]}
if [[ ${MERGE_REQUEST_SOURCE} =~ .*${FEATURE_LIST[*]}.* ]]; then
FEATURE=$((${FEATURE}+1))
echo "${MAJOR}.${FEATURE}.${HOTFIX}" > version
elif [[ ${MERGE_REQUEST_SOURCE} =~ .*${HOTFIX_LIST[*]}.* ]]; then
HOTFIX=$((${HOTFIX}+1))
echo "${MAJOR}.${FEATURE}.${HOTFIX}" > version
else
echo -e "Nothing change, exit."
exit 0
fi
I've declared two arrays, FEATURE_LIST that contain only feature and work, if i type ./script.sh feature or ./script.sh feature/foobar it increase the value, instead if i type ./script.sh hotfix or other values combinations of array HOTFIX_LIST nothing happened. Where the error?
Using .*${HOTFIX_LIST[*]}.* is quite a tedious way of representing a string for an alternate match for the regex operator in bash. You can use the | character to represent alternations (because Extended Regular Expressions library is supported) in bash regex operator.
First generate the alternation string from the array into a string
hotfixList=$(IFS="|"; printf '^(%s)$' "${HOTFIX_LIST[*]}")
echo "$hotfixList"
^(fix|hotfix|bugfix)$
The string now represents a regex pattern comprising of three words that will match exactly as is because of the anchors ^ and $.
You can now use this variable in your regex match
[[ ${MERGE_REQUEST_SOURCE} =~ $hotfixList ]]
also for the feature check, just put the whole array expansion with [*] on the RHS which would be sufficient. Also you don't need the greedy matches, since you have the longer string on the LHS the comparison would still hold good.
[[ ${MERGE_REQUEST_SOURCE} =~ ${FEATURE_LIST[*]} ]]
As a side note, always use lower case variable names for user variables. The uppercase names are reserved only for the variables maintained by the shell which are persistent and have special meaning.
Is there a better way to check if two strings are equal while ignoring a given delimiter, example:
function is_equal() {
local str1="$1"
local delim1="$2"
local str2="$3"
local delim2="$4"
IFS=$delim1 read -r -a array1 <<< "$str1"
IFS=$delim2 read -r -a array2 <<< "$str2"
if [[ ${#array1[#]} -ne ${#array2[#]} ]]; then
return 1
fi
str1raw=$(IFS='' echo "${array1[*]}")
str2raw=$(IFS='' echo "${array2[*]}")
if [ "${str1raw}" != "${str2raw}" ]; then
return 1
fi
return 0
}
is_equal "!etc!daemon!sys.conf" "!" "/etc/daemon/sys.conf" "/"
This works but I'd like to not work with arrays.
As long as it is safe that your strings contain text data, meaning printable chars only, you can use parameter expansion to replace the delimiters by non-printable characters before the comparison. Doing so both string share the same delimiter during the comparison:
if [ "${str1//$delim1/\\x01}" = "${str2//$delim2/\\x01}" ] ; then
echo "strings are equal"
fi
I'm using a non-printable character for the delimiter to make sure that the delimiter will not be part of the data itself.
You need to use arrays. Usually, an array is used to store a set of items which might whatever character you would think to separate two items. Here, you have a mutual problem: each string already has a safe delimiter for itself, but that elimiter isn't necessarily safe for the other.
In order to do component-wise comparisons, you need to choose a common delimiter that is safe for both--which takes us back to the problem arrays were introduced to solve.
If you really want avoid working with arrays, you can do this:
function is_equal {
local str1="$1"
local delim1="$2"
local str2="$3"
local delim2="$4"
diff <( tr -d "$delim1" <<< "$str1" ) \
<( tr -d "$delim2" <<< "$str2" ) > /dev/null 2>&1
}
It might be slower, but it's shorter and easier to read IMHO.
I have the following problem: (Its about dates)
The user will set the following variable.
variable1=33_2016
now I Somehow want to to automatically set a second variable which sets the "33" +1
that I get
variable2=34_2016
Thanks for any advice.
My first choice would be to break the first variable apart with read, then put the (updated) pieces back together.
IFS=_ read f1 f2 <<< "$variable1"
# Option 1
variable2=$((f1 + 1))_$f2
# Option 2
printf -v variable2 '%s_%s" "$((f1 + 1))" "$f2"
You can also use parameter expansion to do the parsing:
f1=${variable%_*}
f2=${variable#*_}
You can also use a regular expression, which is more readable for parsing but much longer to put the pieces back together (BASH_REMATCH could use a shorter synonym).
[[ $variable1 =~ (.*)_(.*) ]] &&
f1=$((${BASH_REMATCH[1]}+1)) f2=${BASH_REMATCH[2]}
The first and third options also allow the possibility of working with an array:
# With read -a
IFS=_ read -a f <<< "$variable1"
variable2=$(IFS=_; echo "${f[*]}")
# With regular expression
[[ $variable1 =~ (.*)_(.*) ]]
variable2=$(IFS=_; echo "${BASH_REMATCH[*]:1:2}")
You can use awk:
awk 'BEGIN{FS=OFS="_"}{$1+=1}1' <<< "${variable1}"
While this needs an external process to spawn (a bit slower) it's easier to read/write. Decide for yourself what is more important for you here.
To store the return value in a variable, use command substitution:
variable2=$(awk 'BEGIN{FS=OFS="_"}{$1+=1}1' <<< "${variable1}")
You can do somewhat the same thing with parameter expansion with substring substitution, e.g.
$ v1=33_2016
$ v2=${v1/${v1%_*}/$((${v1%_*}+1))}
$ echo $v2
34_2016
It's six to one, a half-dozen to another.
I'm looking at a simple for loop with the following logic:
variable=`some piped string`
array_value=(1.1 2.9)
for i in ${array_value[#]}; do
if [[ "$variable" == *some_text*"$array_value" ]]; then
echo -e "Info: Found a matching string"
fi
The problem is that I cannot get this to show me when it finds either the string ending in 1.1 or 2.9 as sample data.
If I do an echo $array_value in the for loop I can see that the array values are being taken so its values are being parsed, though the if loop doesn't return that echo message although the string is present.
LE:
Based on the comments received I've abstracted the code to something like this, which still doesn't work if I want to use wildcards inside the comparison quote
versions=(1.1 2.9)
string="system is running version:2.9"
for i in ${versions[#]}; do
if [[ "$string" == "system*${i}" ]]; then
echo "match found"
fi
done
Any construction similar to "system* ${i}" or "* ${i}" will not work, though if I specify the full string pattern it will work.
The problem with the test construct has to you with your if statement. To construct the if statement in a form that will evaluate, use:
if [[ "$variable" == "*some_text*${i}" ]]; then
Note: *some_text* will need to be replaced with actual text without * wildcards. If the * is needed in the text, then you will need to turn globbing off to prevent expansion by the shell. If expansion is your goal, then protect the variable i by braces.
There is nothing wrong with putting *some_text* up against the variable i, but it is cleaner, depending on the length of some_text, to assign it to a variable itself. The easiest way to accommodate this would be to define a variable to hold the some_text you are needing. E.g.:
prefix="some_text"
if [[ "$variable" == "${prefix}${i}" ]]; then
If you have additional questions, just ask.
Change "system*${i}" to system*$i.
Wrapping with quotes inside [[ ... ]] nullifies the wildcard * by treating it as a literal character.
Or if you want the match to be assigned to a variable:
match="system*"
you can then do:
[[ $string == $match$i ]]
You actually don't need quotes around $string either as word splitting is not performed inside [[ ... ]].
From man bash:
[[ expression ]]
...
Word splitting and pathname expansion are not
performed on the words between the [[ and ]]
...
Any part of the pattern may be quoted to force
the quoted portion to be matched as a string.
can anybody explain why the following bash code involving compound operators is not behaving as expected? basically, nothing enters the if statement inside the for loop but i am passing it correct parameters that should return something by running:
./my_bash_script 20100101 20120101
dates.txt is a list of all days since 2000
#!/bin/bash
old_IFS=$IFS
IFS=$'\n'
lines=($(cat dates.txt)) # array
IFS=$old_IFS
for (( i=1; i<${#lines[#]}; i++ ))
do
if [[ ${line[$i]} -ge $1 && ${line[$i]} -le $2 ]]; then
echo 0 > ${line[$i]} # redirect to file
echo ${line[$i]}
fi
done
The problem is that you've declared an array named lines, but then you try to access it as though it were named line. You need to change every occurrence of ${line[$i]} to ${lines[$i]}.
Better yet, you can dispense with the arithmetic for-loop, and write:
for line in "${lines[#]}" ; do
which will let you refer to the line as $line or "$line" rather than as ${lines[$i]}.
(By the way, how come you have that logic to modify $IFS? It seems like its default value would work just as well.)