I have a version number with three columns and two digits (xx:xx:xx). Can anyone please tell me how to increment that using shell script.
Min Value
00:00:00
Max Value
99:99:99
Sample IO
10:23:56 -> 10:23:57
62:54:99 -> 62:55:00
87:99:99 -> 88:00:00
As a one liner using awk, assuming VERSION is a variable with the version in it:
echo $VERSION | awk 'BEGIN { FS=":" } { $3++; if ($3 > 99) { $3=0; $2++; if ($2 > 99) { $2=0; $1++ } } } { printf "%02d:%02d:%02d\n", $1, $2, $3 }'
Nothing fancy (other than Bash) needed:
$ ver=87:99:99
$ echo "$ver"
87:99:99
$ printf -v ver '%06d' $((10#${ver//:}+1))
$ ver=${ver%????}:${ver: -4:2}:${ver: -2:2}
$ echo "$ver"
88:00:00
We just use the parameter expansion ${ver//:} to remove the colons: we're then left with a usual decimal number, increment it and reformat it using printf; then use some more parameter expansions to group the digits.
This assumes that ver has already been thorougly checked (with a regex or glob).
It's easy, just needs some little math tricks and bc command, here is how:
#!/bin/bash
# read VERSION from $1 into VER
IFS=':' read -r -a VER <<< "$1"
# increment by 1
INCR=$(echo "ibase=10; ${VER[0]}*100*100+${VER[1]}*100+${VER[2]}+1"|bc)
# prepend zeros
INCR=$(printf "%06d" ${INCR})
# output the result
echo ${INCR:0:2}:${INCR:2:2}:${INCR:4:2}
If you need overflow checking you can do it with the trick like INCR statement.
This basically works, but may or may not do string padding:
IN=43:99:99
F1=`echo $IN | cut -f1 '-d:'`
F2=`echo $IN | cut -f2 '-d:'`
F3=`echo $IN | cut -f3 '-d:'`
F3=$(( F3 + 1 ))
if [ "$F3" -gt 99 ] ; then F3=00 ; F2=$(( F2 + 1 )) ; fi
if [ "$F2" -gt 99 ] ; then F2=00 ; F1=$(( F1 + 1 )) ; fi
OUT="$F1:$F2:$F3"
echo $OUT
try this one liner:
awk '{gsub(/:/,"");$0++;gsub(/../,"&:");sub(/:$/,"")}7'
tests:
kent$ awk '{gsub(/:/,"");$0++;gsub(/../,"&:");sub(/:$/,"")}7' <<< "22:33:99"
22:34:00
kent$ awk '{gsub(/:/,"");$0++;gsub(/../,"&:");sub(/:$/,"")}7' <<< "22:99:99"
23:00:00
kent$ awk '{gsub(/:/,"");$0++;gsub(/../,"&:");sub(/:$/,"")}7' <<< "22:99:88"
22:99:89
Note, corner cases were not tested.
Related
I want to evaluate the expression and display the output correct to 3 decimal places i tried with below code but it is not working help me how to do that in bash.
echo -e "Enter expression to calculate : \c"
read num
let a=num
printf '%f\n' "$a"
Input : 5+50*3/20 + (19*2)/7
Output : 17.000000
Desired Output : 17.929
With bc:
echo 'scale=3; 5+50*3/20 + (19*2)/7' | bc -l
Output:
17.928
Instead of let a=num, you could for example:
echo $num | bc
17.92857142857142857142
This is one of the very rare cases where it's acceptable to let a shell variable expand to become part of the body of an awk script:
$ num='5+50*3/20 + (19*2)/7'
$ awk 'BEGIN{print '"$num"'}'
17.9286
$ awk 'BEGIN{printf "%0.3f\n", '"$num"'}'
17.929
I have a delimited string stored in a variable.
date_str = 2017-04-03,2017-04-04,2017-04-05
How do I take the min value out of this delimited string using linux
Expected output is --> 2017-04-03
Could some one help me to do that?
Short gawk approach:
awk -v d=$date_str 'BEGIN{split(d,a,","); asort(a); print a[1]}'
The output:
2017-04-03
split(d,a,",") - splits "date" string into pieces separated by ,
asort(a) - sorts an array values
a[1] - represents the first item of sorted array
Using awk:
$ awk -v d=$date_str ' # set variable to awk var d
BEGIN {
n=split(d,a,",") # split to a on ,
for(i=1;i<=n;i++) # iterate thru a
if(m==""||a[i]<m) # compare to current min m
m=a[i]
print m # after everything print min m
}'
2017-04-03
Regarding comments:
$ echo $date_str | awk -v RS=, 'NR==1||$0<m{m=$0}END{print m}'
2017-04-03
Use this command:
echo "date_str = 2017-04-03,2017-04-04,2017-04-05" | grep -Po "[0-9][^,]+" | sort -n | head -n 1
result is:
2017-04-03
If you need sorting by date
#!/bin/sh
STR='2017-04-03,2017-04-04,2017-04-05,2017-02-23,2017-04-25,2017-03-12,2016-08-25';
TS_ARR=();
IFS=',' read -r -a dates <<< $STR
for next_date in ${dates[#]}; do
date_ts=`date --date="${next_date}" +%s`
TS_ARR+=($date_ts)
done
IFS=$'\n' SORT_TS=($(sort <<< "${TS_ARR[*]}"))
echo "sorted: `date -d #${SORT_TS[0]} +%Y-%m-%d`"
Should show you sorted: 2016-08-25
Here's a solution that converts the strings to seconds from epoch, sorts them, grabs the first one and converts it back to a string:
date_str="2017-04-03,2017-04-04,2017-04-05"
while IFS=, read -r -a arr || [[ -n $arr ]]; do
for str in ${arr[*]}; do
echo $(date -d "$str" +%s)
done
done <<<"$date_str" |
sort -n |
head -n 1 |
{ read -r earliest; date -d #"${earliest}" +%F ; }
I am facing some issue when I am reading the 3rd word(a hex string) of each line in a text file and compare it with a hex number. Can some one please help me on it.
#!/bin/bash
A=$1
cat $A | while read a; do
a1=$(echo \""$a"\" | awk '{ print $3 }')
#echo $a > cut -d " " -f 3
echo $a1
(("$a1" == 0x10F7))
echo $?
done
But when I use below, the comparison happens correctly,
a1= 0xADCAFE
(( "$a1" == 0x10F7 ))
echo $?
Then why it is showing issue when I read like below,
a1=$(echo \""$a"\" | awk '{ print $3 }')
or> a1=$(echo $a | awk '{ print $3 }')
echo $a prints intended hex value, but comparison does not happen.
Regards,
Running Awk inside a while read loop is an antipattern. Just do the loop in Awk; it's good at that.
awk '$3 == 4343' "$1"
If you want to compare against a string whose value is "0x10F7" then it's
awk '$3 == "0x10F7"' "$1"
If you want to match either, case insensitively etc, a regex is a good way to do that.
awk '$3 ~ /^(0x10[Ff]7|4343)$/' "$1"
Notice how the $1 in double quotes is handled by the shell, and gets replaced by a (properly quoted!) copy of the script's first command-line argument before Awk runs, while the Awk script in single quotes has its own namespace, so $3 is an Awk variable which refers to the third field in the current input line.
Either way, avoid the useless use of cat and always always always quote variables which contain file names with double quotes.
That's literal double quotes. You seem to have tried both a dangerous bare $a and a doubly double-quoted "\"$a\"" where the simple "$a" would be what you actually want.
Thank you all for your responses, Now my script is working fine. I was trying to match two files, below script does the purpose
#!/bin/bash
A=$1
B=$2
dos2unix -f "$A"
dos2unix -f "$B"
rm search_match.txt search_data_match.txt search_nomatch.txt search_data_nomatch.txt
while read line;do
search_word=$(echo $line | awk '{ print $1 }')
grep "$search_word" $B >> temp_file.txt
while read var;do
file1_hex=$(echo $line | awk '{ print $2 }')
file2_hex=$(echo $var | awk '{ print $3 }')
(("$file1_hex" == "$file2_hex"))
zero=$(echo $?)
if [ "$zero" -eq 0 ] ; then
echo $line >> search_match.txt
echo $var >> search_data_match.txt
else
echo $line >> search_nomatch.txt
echo $var >> search_data_nomatch.txt
fi
done < "temp_file.txt"
rm temp_file.txt
done < "$A"
I am working on a shell script that converts exported Microsoft in-addr.apra.txt files to a more useful format so that i can use it in the future in other products for automation purposes. No i am figuring a problem which (im not a programmer) can not solve in a simple way.
Sample script
x=123.223.224
rev $x
gives me
422.322.321
but i want to have the output as follow:
224.223.123
is there a easy way to do it without rev or putting each group in a variable? Or is there a sample i can use? or maybe i use the wrong tools to do it?
Using awk:
x='123.223.224'
awk 'BEGIN{FS=OFS="."} {for (i=NF; i>=2; i--) printf $i OFS; print $1}' <<< "$x"
224.223.123
Use awk for this!
If your text file always contains three octets, simply use . as separator:
echo $x | awk -F. '{ print $3 "." $2 "." $1 }'
For more complex cases, use internal split():
echo $x | awk '{
n = split($0, a, ".");
for(i = n; i > 1; i--) {
printf "%s.", a[i];
}
print a[1]; }'
In this sample split() will split every line (which is passed as argument $0) using delimiter ., saves resulting array into a and returns length of that array (which is saved to n). Note that unlike C,
split() array indexes are starting with one.
Or python:
python -c "print '.'.join(reversed('$x'.split('.')))"
Here is my script.
#!/bin/sh
value=$1
delim=$2
total_fields=$(echo "$value" | tr -cd $2 | wc -c)
let total_fields=total_fields+1
i=1
reverse_value=""
while [ $total_fields -gt 0 ]; do
cur_value=$(echo "$value" | cut -d${delim} -f${total_fields})
if [ $total_fields -ne 1 ]; then
cur_value="$cur_value${delim}"
fi
#echo "$cur_value"
reverse_value="$reverse_value$cur_value"
#echo "$i --> $reverse_value"
let total_fields=total_fields-1
done
echo "$reverse_value"
Using a few small tools.
tr '.' '\n' <<< "$x" | tac | paste -sd.
224.223.123
I've written a simple account script in bash and I couldn't find out the standard way of how displaying a floating point value. Is this the best solution using awk? Balance is also printing out 0.00, did I forget to include something?
#!/bin/bash
function printBalance()
{
echo | awk 'BEGIN { printf "\nCurrent balance: %.2f\n", balance }'
sleep 1
}
function makeWithdraw()
{
echo -en "\nWithdraw an amount: "
read deposit
if [ "$withdraw" -gt "$balance" ]; then
echo -en "\nInsufficient funds"
sleep 1
else
balance=$(( balance - withdraw ))
fi
}
balance=$((RANDOM%100+1))
# code continues...
Now I see what was missing: awk needs to have the variable balance given:
If you have $myvar you have to:
awk -v awk_internal_var=${myvar} '{printf "%s", awk_internal_var}'
In your case:
echo | awk -v balance=${balance} 'BEGIN { printf "\nCurrent balance: %.2f\n", balance }'
So that's why it was printing 0.00: because it did not get the value.
Just use bash's built-in printf command:
$ printf '%.2f\n' 123.456
123.46
I'd also suggest using printf rather than echo for anything complicated. There are multiple versions of echo in various shells and as separate programs. The same is true of printf, but its behavior is much more consistent.
For example, rather than
echo -en "\nWithdraw an amount: "
you can use:
printf '\nWithdraw an amount: '
or, if there's a possibility the string could contain % characters:
printf '\n%s: ' 'Withdraw an amount'
Greg says awk should do it
$ awk 'BEGIN {printf "%.3f\n", 10 / 3}'
3.333