calling bash script generates error on if statement and variable assignment - linux

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

Related

Difference between "${param[0]}" and ${1} in bash

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.

How can I know if a string contains only one or several words in Bash? [duplicate]

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.

cant create third parameter in bash script (after $*)

I want to create a bash script, that gets 3 parameters. But the second needs to be $*, because i need later these lines. The other two parameters (first and third) doesn't need this.
for x in $* do
The first and second parameter aren't the problem, this one works:
parameter1="$1"
shift
parameter2="$*"
But i need the third parameter at the end and something like this
parameter1="$1"
parameter3="$3"
shift
parameter2="$*"
won't work. My command at the end should look like this:
bash myscript parameter1 parameter2 parameter3
For specifically three parameters, you can use substring parameter expansion in a simple way:
parameter1=$1
parameter2="${#:2:1}" # One parameter, starting with #2
parameter3=$3
Or course, that's unnecessary, since you can just use $2 instead of ${#:2:1}, but I point it out as a simple introduction to the syntax (and not at all because I overlooked the fact you would use $2, really....)
(You can also use it as a substitute for indirect parameter expansion; "${#:n:1}" and "${!n}" are basically equivalent when n is a variable with an integer value.)
For the more general case, where you want an arbitrary number of arguments between the first and last, it gets a little more complicated, although the principle is the same:
parameter1=$1
middleParameters=( "${#:2:$#-2}" ) # n - 2 parameters, starting with #2, i.e., all but $1 and ${!n} for n=$#
lastParameter="${#:$#}"
shift removes an argument from the left. If you want to remove an argument from the right, you can do that with:
set -- "${#:1:$# - 1}"
Thus:
parameter1=$1 # capture leftmost argument
shift # remove leftmost argument
parameter3=${*:$#:1} # capture rightmost argument
set -- "${#:1:$# - 1}" # remove rightmost argument
parameter2=$* # concatenate remaining arguments and store in a string
Note that $* is almost certainly the Wrong Thing. If you want to keep your arguments separate, respecting their quoting, instead use an array:
parameter2=( "$#" )
for item in "${parameter2[#]}"; do
echo "Processing item: $item"
done
If your script is run as yourscript arg1 "item A" "item B" arg3, then the above will ensure that item A and item B are treated as individual arguments, rather than treating item as an argument, A as another, etc.

Finding substring of variable length in bash

I have a string, such as time=1234, and I want to extract just the number after the = sign. However, this number could be in the range of 0 and 100000 (eg. - time=1, time=23, time=99999, etc.).
I've tried things like $(string:5:8}, but this will only work for examples of a certain length.
How do I get the substring of everything after the = sign? I would prefer to do it without outside commands like cut or awk, because I will be running this script on devices that may or may not have that functionality. I know there are examples out there using outside functions, but I am trying to find a solution without the use of such.
s=time=1234
time_int=${s##*=}
echo "The content after the = in $s is $time_int"
This is a parameter expansion matching everything matching *= from the front of the variable -- thus, everything up to and including the last =.
If intending this to be non-greedy (that is, to remove only content up to the first = rather than the last =), use ${s#*=} -- a single # rather than two.
References:
The bash-hackers page on parameter expansion
BashFAQ #100 ("How do I do string manipulations in bash?")
BashFAQ #73 ("How can I use parameter expansion? How can I get substrings? [...])
BashSheet quick-reference, paramater expansion section
if time= part is constant you can remove prefix by using ${str#time=}
Let's say you have str='time=123123' if you execute echo ${str#time=} you would get 123123

error in Integer comparison in bash

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.

Resources