Updating (sequentially adding a value into) a variable within a for loop, in bash script - bash

I am trying to update a variable within a for loop, sequentially adding to the variable, and then printing it to a series of file names.
Actually, I want to sequentially update a series of file, from a tmp file distTmp.RST to sequentially dist1.RST, dist2.RST, etc..
The original distTmp.RST contains a line called "WWW". I want to replace the string with values called 21.5 in dist1.RST, 22.5 in dist2.RST, 23.5 in dist3.RST, etc...
My code is as follows:
#!/bin/bash
wm=1
wM=70
wS=1
F=20.5
for W in {${wm}..${wM}..${wS}}; do
F=$(${F} + 1 | bc)
echo ${F}
sed "s/WWW/"${F}"/g" distTmp.RST > dist${W}.RST
done
echo ${F}
========
But I am getting error message as follows:
change.sh: line 13: 20.5 + 1 | bc: syntax error: invalid arithmetic operator (error token is ".5 + 1 | bc")
Kindly suggest me a solution to the same.

Kindly suggest me a solution to the same.
This might do what you wanted. Using a c-style for loop.
#!/usr/bin/env bash
wm=1
wM=70
wS=1
F=20.5
for ((w=wm;w<=wM;w+=wS)); do
f=$(bc <<< "$F + $w")
echo "$f"
sed "s/WWW/$f/g" distTmp.RST > "dist${w}.RST"
done
The error from your script might be because the order of expansion. brace expansion expansion happens before Variable does.
See Shell Expansion

Use
F=$(echo ${F} + 1 | bc)
instead of F=$((${F} + 1 | bc)). The doubled-up parentheses are what caused your error. Double parentheses weren't in the original code, but I get a different error saying 20.5: command not found with the original code, so I tried doubling the parentheses and get the error in the question. Apparently, floating point numbers aren't supported by $(()) arithmetic evaluation expressions in Bash.

Related

Read content of file line by line in unix using 'line'

I have a file - abc, which has the below content -
Bob 23
Jack 44
Rahul 36
I also have a shell script that do the addition of all the numbers here.
The specific line that picks up these numbers is -
while read line
do
num=echo ${line#* }
sum=`expr $sum + $num`
count=`expr $count + 1`
done< "$readfile"
I assumed that the code is just picking up the last field from file, but it's not. If i modify the file like
Bob 23 12
Jack 44 23
Rahul 36 34
The same script fails with syntax error.
NOTE: I know there are other ways to pick up the field value, but i would like to know how this works.
The syntax ${line#* } will skip the shortest string from the beginning till it finds a space and returns the rest. It worked fine when you had just 2 columns. But the same will not work when 3 columns are present as it will return you the last 2 column values which when you use it in the sum operator will throw you an error. To explain that, just imagine
str='foo bar'
printf '%s\n' "${str#* }"
bar
but imagine the same for 3 fields
str='foo bar foobar'
printf '%s\n' "${str#* }"
bar foobar
To fix that use the parameter expansion syntax of "${str##* }" to skip the longest sub-string from beginning. To fix your script for the example with 3 columns, I would use a script as below.
This does a simple input redirection on the file and uses the read command with the default IFS value which is a single white space. So I'm getting only the 3rd field on each line (even if it has multiple fields), the _ mark the fields I'm skipping. You could also have some variables as place-holders and use their value in the scripts also.
declare -i sum
while read -r _ _ value _ ; do
((sum+=value)
done < file
printf '%d\n' "$sum"
See Bash - Parameter Expansion (Substring removal) to understand more.
You could also use the PE syntax ${line##* } as below,
while read -r line ; do
((sum+=${line##* }))
done < file
[Not relevant to the current question]
If you just want the sum to be computed and not specifically worried about using bash script for this. You can use a simple Awk command to sum up values in 3rd column as
awk '{sum+=$3}END{print sum}' inputfile

How nest ancillary command in bash arithmetic command

I want to print the uptime for a jvm which is running on my machine. I can do that using jcmd. However I want to print it out in minutes. So, I tried the following:
bash-3.2$echo $(($(jcmd 785 VM.uptime)/60))
However this isn't working. I get the following error:
bash-3.2$ echo $(($(jcmd 785 VM.uptime)/60))
bash: 785:
1541.343 s/60: syntax error in expression (error token is ":
1541.343 s/60")
If I assign $(jcmd 785 VM.uptime) to a variable first and substitute that into the arithmetic expression, it still doesn't work. Any idea how I can get this to work?
Your output is not an integer, and has a character 's'. You should cut unnecessary part:
echo $(( $(jcmd 785 VM.uptime |sed 's/^\([[:digit:]]*\).*$/\1/')/60 ))
or
echo "scale=4;$(jcmd 785 VM.uptime |sed 's/ s//')/60" |bc
-- this will give you a float value.

Subtract 2 array values having floating point decimals

I'm reading a file with multiple columns, and dumping 2 columns of the file into into 2 different arrays. Now based on a condition, I need to get the difference between 2 values retrieved from the array. So my code looks like this -
if [ condition ]; then
VAL = (( ${local[$x]} - ${local[$y]} ))
fi
The thing is, while I'm able to echo and see both values ${local[$x]} and ${local[$y]}, the subtraction operation gives me a syntax error. I understand it's failing because the values currently held within the array involve floating point decimal values - like 3456712.126758, and the assignment throws errors with the decimal part. I understand arithmetic operations are not a strong point with the bash shell as floating point numbers are considered strings hence the issue.
Could you please help getting the right format please?
Should I do something like this
VAL= awk '{ print ${local[$x]} - ${local[$y]} }'
or
VAL=echo ${local[$x]} - ${local[$y]} | bc -l
I'm sure the syntax above is wrong, kindly help with the syntax, I need it assigned the subtracted result assigned to the field VAL.
Not only floating points, but also the spacing would lead to syntax errors. Bash variable assignments must have no spaces, as in val=x, and not val = x.
Uppercase variable names are reserved for environment variables, and it is recommended to use lowercase instead for your own variables. (Oh, and local is also a reserved word.)
Your assignment wouldn't work with proper spacing, either: the arithmetic expression
var=(( ${vals[$x]} - ${vals[$y]} )) # syntax error near unexpected token `('
is just evaluating its contents, but not returning anything. You could use the part after the = as a condition. To make it return something, you need arithmetic expansion (note the extra $):
var=$(( ${vals[$x]} - ${vals[$y]} )) # works for integers
^
In an arithmetic context, you don't even need to prepend $ to your variables:
var=$(( vals[x] - vals[y] ))
works just as well. Exception: in associative arrays, you still have to do it for indices:
$(( vals[$x] ))
And finally, as you noticed, this all doesn't work for floating point numbers. Instead of piping to bc, you can also use a here string and avoid spawning a subshell:
$ vals=(1.1 2.2)
$ x=0
$ y=1
$ echo $(( local[x] - local[y] )) # No '$' needed for variable expansion
bash: 1.1: syntax error: invalid arithmetic operator (error token is ".1") # But :(
$ bc -l <<< "local[x] - local[y]" # Requires '$' - these expand to nothing
0
$ bc -l <<< "${local[x]} - ${local[y]}" # Works!
-1.1
With awk:
awk -v a=${a} -v b=${b} 'BEGIN{print a - b}'
With bc:
echo "${a} - ${b}" | bc -l
See also other options here.

sed backreferences and command interpolation

I am having an interesting issue using only sed to substitute short month strings (ex "Oct") with the corresponding number value (ex "10) given a string such as the following:
Oct 14 09:23:35 some other input
To be replaced directly via sed with:
14-10-2013 09:23:25 some other input
None of the following is actually relevant to solving the trivial problem of month string -> number conversion; I'm more interested in understanding some weird behavior I encountered while trying to solve this problem entirely with sed.
Without any attempt of this string substitution (the echo statement is in lieu of the actual input in my script):
...
MMM_DD_HH_mm_SS="([A-Za-z]{3}) ([0-9]{2}) (.+:[0-9]{2})"
echo "Oct 14 09:23:35 some other input" | sed -r "s/$MMM_DD_HH_mm_ss (.+)/\2-\1-\3 \4/"
Then how to transform the backreference \1 into a number. Of course one thinks of using command interpolation with the backreference as an argument:
...
TestFunc()
{
echo "received input $1$1"
}
...
echo "Oct 14 09:23:35 some other input" | sed -r "s/$MMM_DD_HH_mm_ss (.+)/\2-$(TestFunc \\1)-\3 \4/"
Where TestFunc would be a variation of the date command (as proposed by Jotne below) with the echo'd date-time group as an input. Here TestFunc is just an echo because I'm much more interested in the behavior of what the function believes to be the value of $1.
In this case the sed with TestFunc produces the output:
14-received input OctOct-09:23:35 some other input
Which suggests that sed actually is inserting backreference \1 into the command substitution $(...) for handling by TestFunc (which appears to receive \1 as the local variable $1).
However, all attempts to do anything more with the local $1 fail. For example:
TestFunc()
{
echo "processed: $1$1" > tmp.txt # Echo 1
if [ "$1" == "Oct" ]; then
echo "processed: 10"
else
echo "processed: $1$1" # Echo 2
fi
}
Returns:
14-processed: OctOct-09:23:35 some other input
$1 has been substituted into Echo 2, yet tmp.txt contains the value processed: \1\1; as if the backreference is not being inserted into the command substitution. Even weirder, the if condition fails with $1 != "Oct", yet it falls through to an echo statement which indicates $1 = "Oct".
My question is why is the backreference insertion working in the case of Echo 2 but not Echo 1? I suspect that the backreference insertion isn't working at all (given the failure of the if statement in TestFunc) but rather something subtle is going on that makes the substitution appear to work correctly in the case of Echo 2; what is that subtlety?
Solution
On further reflection I believe I understand what is going on:
\\1 is passed to the command substitution subroutine / child function as the literal \1. This is why equality test within the child function is failing.
however the echo function is correctly handling the string \\1 as $1. So echo "aa$1aa" returns the result of the command substitution to sed as aa\1aa. Other functions such as rev also "see" $1 as \1.
sed then interpolates \1 in aa\1aa as Oct or whatever the backreference is, to return aaOctaa to the user.
Since command substitution within regexes clearly works, it would be really cool if sed replaced the value of \\1 (or \1, whatever) with the backreference before executing the command substitution $(...); this would significantly increase sed's power...
This might work for you (GNU sed):
s/$/\nJan01...Oct10Nov11Dec12/;s/(...) (..) (..:..:.. .*)\n.*\1(..).*/\2-\4-2013 \3/;s/\n.*//' file
Add a lookup to the end of the line and use the back reference to match on it making sure to remove the lookup table in all cases.
Here's an example of passing a backreference to a function:
f(){ echo "x$1y$1z"; }
echo a b c | sed -r 's/(.) (.) (.)/'"$(f \\2)"'/'
returns:
xbybz
HTH
Use the correct tool:
date -d "Oct 14 09:23:35" +"%d-%m-%Y %H:%M:%S"
14-10-2013 09:23:35
Date does read your input and convert it to any format you like

bash: calling a scripts with double-quote argument

I have a bash scripts which an argument enclosed with double quotes, which creates a shape-file of map within the given boundries, e.g.
$ export_map "0 0 100 100"
Within the script, there are two select statements:
select ENCODING in UTF8 WIN1252 WIN1255 ISO-8859-8;
...
select NAV_SELECT in Included Excluded;
Naturally, these two statements require the input to enter a number as an input. This can by bypassed by piping the numbers, followed by a newline, to the script.
In order to save time, I would like to have a script that would create 8 maps - for each combination of ENCODING (4 options) and NAV_SELECT (2 options).
I have written another bash script, create_map, to server as a wrapper:
#!/bin/bash
for nav in 1 2 3 4;
do
for enc in 1 2;
do
printf "$nav\n$enc\n" | /bin/bash -c "./export_map.sh \"0 0 100 100\""
done
done
**This works (thanks, Brian!), but I can't find a way to have the numeric argument "0 0 100 100" being passed from outside the outer script. **
Basically, I'm looking for way to accept an argument within double quotes to a wrapper bash script, and pass it - with the double quotes - to an inner script.
CLARIFICATIONS:
export_map is the main script, being called from create_map 8 times.
Any ideas?
Thanks,
Adam
If I understand your problem correctly (which I'm not sure about; see my comment), you should probably add another \n to your printf; printf does not add a trailing newline by default the way that echo does. This will ensure that the second value will be read properly by the select command which I'm assuming appears in export_map.sh.
printf "$nav\n$enc\n" | /bin/bash -c "./export_map.sh \"100 200 300 400\""
Also, I don't think that you need to add the /bin/bash -c and quote marks. The following should be sufficient, unless I'm missing something:
printf "$nav\n$enc\n" | ./export_map.sh "100 200 300 400"
edit Thanks for the clarification. In order to pass an argument from your wrapper script, into the inner script, keeping it as a single argument, you can pass in "$1", where the quotes indicate that you want to keep this grouped as one argument, and $1 is the first parameter to your wrapper script. If you want to pass all parameters from your outer script in to your inner script, each being kept as a single parameter, you can use "$#" instead.
#!/bin/bash
for nav in 1 2 3 4;
do
for enc in 1 2;
do
printf "$nav\n$enc\n" | ./export_map.sh "$1"
done
done
Here's a quick example of how "$#" works. First, inner.bash:
#!/bin/bash
for str in "$#"
do
echo $str
done
outer.bash:
#!/bin/bash
./inner.bash "$#"
And invoking it:
$ ./outer.bash "foo bar" baz "quux zot"
foo bar
baz
quux zot

Resources