I am trying to perform a simple integer comparison in bash, which I am new to using, and my methods are causing an error. Any advice would be appreciated.
My basic logic is that I am reading in hurricane track points. There may be multiple entries for the same track_id, with different pressure values. What I want to do is store only one entry per track_id into the array track_id_poly; the case with the lowest pressure. So I am looping through each line, and attempting to compare the current pressure (for $int), with the previous track pressure ($int - 1), and if it is lower, replace the previous array value with the new lower pressure. I hope that makes sense. My code is below.
int=0
while read track_id ppres_inter
do
printf -v pres_inter "%.0f" "$pres_inter"
echo pressure $pres_inter
case $int in
0)
Track_id_poly[$int]=$track_id
Pres_inter_poly[$int]=$pres_inter
((int=int+1)) ;;
*)
if [[ $track_id == ${Track_id_poly[$int-1]} ]]
then
if (( $pres_inter -lt ${Pres_inter_poly[$int-1]} ))
then
Track_id_poly[$int-1]=$track_id
Pres_inter_poly[$int-1]=$pres_inter
fi
else
Track_id_poly[$int]=$track_id
Pres_inter_poly[$int]=$pres_inter
((int=int+1))
fi ;;
esac
done <$file_poly
int_poly=$int
echo Number of polygon crossings from set $i is $int_poly
The line that is causing me problems is the integer comparison for $pres_inter.
if (( $pres_inter -lt ${Pres_inter_poly[$int-1]} ))
I get the following error:
line 41: 96800 -lt 98759 : syntax error in expression (error token is "98759 ")
Any tips to fix this problem would be appreciated. Probably a simple fix!
The ((...)) operator does not accept the same syntax as the test, [..], or [[...]] commands. To compare two numbers in ((...)), you would use actual > or < symbols:
$ (( 4 > 2 )) && echo '4 is bigger!'
4 is bigger!
See the ARITHMETIC EVALUATION section of the bash(1) man page for more information (or here).
My shell scripting is rusty for good reason but you may to review that line and either use "[[ ]]" instead of "(( ))", or use "<" instead of "-lt" . See bash: double or single bracket, parentheses, curly braces
However, the main tip I'd give to you is to stop using bash for things that involve anything beyond simple program invocation and switch to a scripting language (Perl, Python, ...) because it won't only be more robust, it'll be easier to get the job done and it'll also run faster.
In bash "((expression))" is differently evaluated. So you cannot use the operator "-lt", instead you can use the normal operator <.
For further info see the man page of bash:
((expression))
The expression is evaluated according to the rules
described below under ARITHMETIC EVALUATION. If the value of the
expression is non-zero, the return status is 0; otherwise the return
status is 1. This is exactly equivalent to let "expression".
And the paragraph ARITHMETIC EVALUATION explains further possibilities.
Related
I'm looking at some old scripts and I found some parameter assignment that I have not seen before. A while loop reads from a text file and passes the values to a function. The items in the text file look like this:
user_one:abcdef:secretfolder
the first stage of the function then looks like this:
IFS=':' read -a param <<< $#
user="${param[0]}"
pass="${param[1]}"
user_folders="${param[2]}"
I have not seen this sort of assignment before and was wondering if this is just an alternative way of handling it. Is the above the same as this?
IFS=':' read -a param <<< $#
user="${1}"
pass="${2}"
user_folders="${3}"
(change in values to 1-3 due to ${0} being the name of the file itself). This script is 5 years old; This original sort of assignment just seems a longer way to to it, unless I've missed something
I'm still learning shell scripting but as I understand, setting IFS=':' will split the fields on : rather than whitespace and so in the examples, the value of "${param[0]}" and ${1} passed to the function would be user_one
Can someone please explain if there is a reason why "${param[0]}" should be used instead of ${1}?
The command:
IFS=':' read -a param <<< $#
reads the :-separated fields from the command arguments ($#) into the array variable named param. Bash arrays work just like lists in other languages, and you index them with brackets. ${param[0]} is the first field, ${param[1]} then next, and so on. Arrays like this can contain anything, and it's just because of the $# in the read command that this param array happens to contain the arguments. It could just as easily contain foo, bar, and baz if it were created like:
param=(foo bar baz)
The ${1}, ${2} etc. syntax always refers to the script arguments though.
This question already has answers here:
A confusion about ${array[*]} versus ${array[#]} in the context of a bash completion
(2 answers)
Closed 6 years ago.
When I get the content of an array in a string, I have the 2 solutions bellow :
$ a=('one' 'two')
$ str1="${a[*]}" && str2="${a[#]}"
After, of course, I can reuse my string on the code
but how can I know if my variable has only one or several words?
In both cases, the contents of the array are concatenated to a single string and assigned to the variable. The only difference is what is used to join the elements. With ${a[*]}, the first character of IFS is used. With ${a[#]}, a single space is always used.
$ a=(one two)
$ IFS="-"
$ str1="${a[*]}"
$ str2="${a[#]}"
$ echo "$str1"
one-two
$ echo "$str2"
one two
When expanding $str1 or $str2 without quoting, the number of resulting words is entirely dependent on the current value of IFS, regardless of how the variables were originally defined. "$str1" and "$str2" each expand, of course, to a single word.
To add to #chepner's great answer: the difference between ${arr[*]} and ${arr[#]} is very similar to the difference between $* and $#. You may want to refer to this post which talks about $* and $#:
What's the difference between $# and $* in UNIX?
As a rule of thumb, it is always better to use "$#" and "${arr[#]}" than their unquoted or * counterparts.
"${a[*]}" expands to one string for all entries together and "${a[#]}" expands to one string per entry.
Assume we had a program printParameters, which prints for each parameter ($1, $2, and so on) the string my ... parameter is ....
>_ a=('one' 'two')
>_ printParameters "${a[*]}"
my 1. parameter is one two
>_ printParameters "${a[#]}"
my 1. parameter is one
my 2. parameter is two
If you would expand the array manually, you would write
${a[*]} as "one two" and
${a[#]} as "one" "two".
There also differences regarding IFS and so on (see other answers). Usually # is the better option, but * is way faster – use the latter one in cases where you have to deal with large arrays and don't need separate arguments.
By the way: The script printParameters can be written as
#! /bin/bash
argIndex=0
for argValue in "$#"; do
echo "my $((++i)). argument is $argValue"
done
It's very useful for learning more about expansion by try and error.
So I'm trying to do something, not sure if it's possible. I have the following code:
for i in {0..5}; do
if [[ -f ./user$i ]]; then
group$i=$(grep -w "group" ./user0|awk '{print $2}'|perl -lape 's/\s+//sg')
What I want to do is assign a unique variable for each instance of the {0..5} so group1 group2 group3 group4 for each variable name. Then I would change ./user0 to ./user$i and create a dynamic list of variables based on my sequence.
Is this possible? I get the following error when trying to execute this and I'm unsure of what I have actually done that bash doesn't like.
test.sh: line 16: group0=j: command not found
Kurt Stutsman provides the right pointer in a comment on the question: use Bash arrays to solve your problem.
Here's a simplified example:
groups=() # declare an empty array; same as: declare -a groups
for i in {0..5}; do
groups[i]="group $i" # dynamically create element with index $i
done
# Print the resulting array's elements.
printf '%s\n' "${groups[#]}"
See the bottom of this answer for other ways to enumerate the elements of array ${groups[#]}.
bash arrays can be dynamically expanded (and can even be sparse - element indices need not be contiguous)
Hence, simply assigning to element $i works, without prior sizing of the array.
Note how $i need not be prefixed with $ in the array subscript, because array subscripts are evaluated in an arithmetic context (the same context in which $(( ... )) expressions are evaluated).
As for what you did wrong:
group$i=...
is not recognized as a variable assignment by Bash, because - taken literally - group$i is not a valid identifier (variable name).
Because it isn't, Bash continues to parse until the next shell metacharacter is found, and then interprets the resulting word as a command to execute, which in your case resulted in error message group0=j: command not found.
If, for some reason, you don't want to use arrays to avoid this problem entirely, you can work around the problem:
By involving a variable-declaring builtin [command] such as declare, local, or export, you force Bash to perform expansions first, which expands group$i to a valid variable name before passing it to the builtin.
user2683246's answer demonstrates the next best approach by using declare (or, if local variables inside a function are desired, local) to create the variables.
Soren's answer uses export, but that is only advisable if you want to create environment variables visible to child processes rather than mere shell variables.
Caveat: With this technique, be sure to double-quote the RHS in order to capture the full value; to illustrate:
i=0; declare v$i=$(echo 'hi, there'); echo "$v0" # !! WRONG -> 'hi,': only UP TO 1ST SPACE
i=0; declare v$i="$(echo 'hi, there')"; echo "$v0" # OK -> 'hi, there'
Other ways to enumerate the groups array created above:
# Enumerate array elements directly.
for element in "${groups[#]}"; do
echo "$element"
done
# Enumerate array elements by index.
for (( i = 0; i < ${#groups[#]}; i++ )); do
echo "#$i: ${groups[i]}"
done
Use declare group$i=... instead of just group$i=...
Try to use the export or declare function like this
for i in {0..5}; do
if [[ -f ./user$i ]]; then
export group$i=$(grep -w "group" ......
with declare
for i in {0..5}; do
if [[ -f ./user$i ]]; then
declare group$i=$(grep -w "group" ......
where export makes the value available to sub-processes, and declare just available within the same script.
I'm trying to execute a very simple program (round numbers to lowest integer divisible by 15) but am getting an error:
$min = date +"%M";
if [ $min%15 != 0 ]
then
$min - $min%1
fi
echo $min;
I call it with sh cache.sh
I feel I've followed the syntax I've learned here but I'm getting line 9: syntax error: unexpected end of file What have I got wrong here?
That script is not valid bash syntax. I would start by finding some working examples, and perhaps an entire tutorial. You might start with William Shotts' book, which is available online.
Some notes about your attempt:
The $ is used to request replacement of a variable1 by its value. It is not a sigil that is part of the variable name, as it is in Perl or PHP. So it is not used on the left-hand-side of an assignment.
The shell is primarily used to run other executables, and interprets everything through that lens. If a command line looks like an invocation of another program, the shell will try to run that other program, rather than do anything shell-scripty. Therefore, the command min = date +"%M" will cause the shell to look for a program named min and execute it with three command-line arguments: =, date, and +%M.
In order for an assignment to be recognized as such, there cannot be any space around the =.
Without spaces, min=date +"%M" is still not right, however. The shell will just temporarily assign the literal string "date" to the variable min and then try to run a command called +%M.
If a value has spaces in it, you need quotation marks around it2.
Even with quotes, however,min="date +%M" would assign to min the literal string "date +%M". If you actually want to run the command date +"%M" and use its output as a value, then you have to request that using the command-substitution syntax, $(...). Here our friend the dollar sign is again requesting replacement by a dynamic value, but the parentheses make it a different type of request; instead of a variable's value, the expression is replaced by the output of a command.
Because of the parsing issues noted above, the built-in arithmetic operations only work in certain contexts. Two ways to create a valid arithmetic context are the ((...)) special forms and the let command.
Finally, even if your script were syntactically valid, it is semantically incorrect if your goal is to round down to the nearest multiple of 15. The remainder after dividing by 1 is always zero, so your script ends by attempting to subtract 0 from min - and does nothing with the result anyway, since there's no assignment back to min. If you want to round down, you have to actually subtract the remainder that you just tested. You could do it like this:
min=$(date +%M)
let rem=min%15
if (( rem != 0 )); then
let min-=rem
fi
echo $min
But you could also do it a bit more succinctly:
echo $(( min=$(date +%M), min-=min%15 ))
This works without an if because subtracting 0 is harmless. The comma just lets us put two expressions inside a single set of ((...)). The second expression min-=min%15 is a modifying assignment - it means the same thing as min=min-min%15, but saves us one instance of typing out "min". Putting our friend the replacement-requesting $ in front of ((...)) causes the whole expression to be replaced by its value, so that echo gets something to print out. The value of a list of expressions is the value of the last expression, and the value of an assignment is the same as the value that was assigned, so the result that is echoed is the same as the final value of $min: the closest multiple of 15 minutes after the hour.
1 In shell terminology, variables are actually called "parameters". Just something to bear in mind when reading documentation.
2 You actually don't need quotation marks around the %M in your command for this reason. Everything in the shell is automatically a string; you don't need the quotes to make it one. However, they don't hurt, and putting quotation marks around things is a good habit to have, since it keeps your code from being broken by unexpected special characters in input values.
Your script has many syntax issues. In shell assignment is is
var='val'
instead of
$var='val'
Also there is no space around =
Your correct script can be:
min=$(date +"%M")
if (( min % 15 != 0 ))
then
echo "not fully divisible by 15"
fi
echo $min
min=`date +"%M"`;
if [ $min%15 != 0 ]
then
min=$((min - min%1))
fi
echo $min;
It looks like you converted this from some other language. Here's a working bash version.
#!/bin/bash
min=$(date +"%M")
if [ $(($min % 15)) != 0 ] ; then
min=$(( min - min % 1 ))
fi
echo $min;
local output:
~/tmp › sh ./test.sh
34
If you declare an array like this
declare -a test
you can echo the value like this
i=2
echo ${test[i]}
or
i="1+1"
echo ${test[i]}
why the second form is accepted?
i need a complex answer thanks
See man bash:
The subscript is treated as an arithmetic expression that must evaluate to a number.
Complex enough?