I'm trying to define a bash function returning an incremented id
that I can access directly using bash substitution:
#!/bin/bash
getId() {
echo "$x"
x=$((x+1))
}
x=0
echo "id1: $(getId)"
echo "id2: $(getId)"
However the variable is not incremented and I cannot figure out why.
id1: 0
id2: 0
Please, does someone have an explanation for this behaviour?
getId() {
echo "$x"
((x++))
}
x=0
echo -n "id1: "
getId
echo -n "id2: "
getId
Output:
id1: 0
id2: 1
There is no easy way I know of to do it in a sub-shell call using the syntax you have (in the echo line).
An alternate would be:
#!/bin/bash
export x=0
incId() {
#echo "$x"
(( x += 1))
}
incId
echo "id1: $x"
incId
echo "id2: $x"
But here you need the out-of-the-echo-line incId function call to get the id incremented.
It also starts counting from 1, not 0.
Using the let shell command is the better way to do math too.
Using (( ... )) is the right way to do shell arithmetic
Might as well make it generic:
incr() { (( $1 += ${2:-1} )); }
Examples:
incr x ; echo $x # => 1
incr x ; echo $x # => 2
incr x 4; echo $x # => 6
incr x -2; echo $x # => 4
Related
I am trying to write a bash script that accepts a number from the keyboard, and then prints a set of integers from 0 to the number entered. I can't figure out how to do it at all.
This is my code:
while [ 1 ]
do
echo -n "Enter a color: "
read user_answer
for (( $user_answer = $user_answer; $user_answer>0; $user_answer--))
echo $user_answer
fi
done
exit
The error I'm recieving is:
number_loop: line 10: syntax error near unexpected token echo'
number_loop: line 10: echo $user_answer'
Assign a separate variable in order to use increment/decrement operators. $user_answer=$user_answer will always be true and it will throw an error when trying to use decrement. Try the following :
#!/bin/bash
while [ 1 ]
do
echo -n "Enter a color: "
read user_answer
for (( i=$user_answer; i>0; i-- ))
do
echo $i
done
done
exit
You missed the do statement between your for and the echo.
bash has many options to write numbers. What you seem to be trying to do is easiest done with seq:
seq $user_answer -1 0
If you want to use your loop, you have to insert a ; do and replace the fi with done, and replace several $user_answer:
for (( i = $user_answer; i>0; i--)); do
echo $i
done
(btw: I assumed that you wanted to write the numbers in reverse order, as you are going backwards in your loop. Forwards is even easier with seq:
seq 0 $user_input
)
This is where a c-style loop works particularly well:
#!/bin/bash
for ((i = 1; i <= $1; i++)); do
printf "%s\n" "$i"
done
exit 0
Example
$ bash simplefor.sh 10
1
2
3
4
5
6
7
8
9
10
Note: <= is used as the for loop test so it 10 it iterates 1-10 instead of 0-9.
In your particular case, iterating from $user_answer you would want:
for (( i = $user_answer; i > 0; i--)); do
echo $i
done
The for loop is a bash internal command, so it doesn't fork a new process.
The seq command has a nice, one-line syntax.
To get the best of the twos, you can use the { .. } syntax:
eval echo {1..$answer}
I want to take the absolute of a number by the following code in bash:
#!/bin/bash
echo "Enter the first file name: "
read first
echo "Enter the second file name: "
read second
s1=$(stat --format=%s "$first")
s2=$(stat -c '%s' "$second")
res= expr $s2 - $s1
if [ "$res" -lt 0 ]
then
res=$res \* -1
fi
echo $res
Now the problem I am facing is in the if statement, no matter what I changes it always goes in the if, I tried to put [[ ]] around the statement but nothing.
Here is the error:
./p6.sh: line 13: [: : integer expression expected
You might just take ${var#-}.
${var#Pattern} Remove from $var the shortest part of $Pattern that matches the front end of $var. tdlp
Example:
s2=5; s1=4
s3=$((s1-s2))
echo $s3
-1
echo ${s3#-}
1
$ s2=5 s1=4
$ echo $s2 $s1
5 4
$ res= expr $s2 - $s1
1
$ echo $res
What's actually happening on the fourth line is that res is being set to nothing and exported for the expr command. Thus, when you run [ "$res" -lt 0 ] res is expanding to nothing and you see the error.
You could just use an arithmetic expression:
$ (( res=s2-s1 ))
$ echo $res
1
Arithmetic context guarantees the result will be an integer, so even if all your terms are undefined to begin with, you will get an integer result (namely zero).
$ (( res = whoknows - whocares )); echo $res
0
Alternatively, you can tell the shell that res is an integer by declaring it as such:
$ declare -i res
$ res=s2-s1
The interesting thing here is that the right hand side of an assignment is treated in arithmetic context, so you don't need the $ for the expansions.
I know this thread is WAY old at this point, but I wanted to share a function I wrote that could help with this:
abs() {
[[ $[ $# ] -lt 0 ]] && echo "$[ ($#) * -1 ]" || echo "$[ $# ]"
}
This will take any mathematical/numeric expression as an argument and return the absolute value. For instance: abs -4 => 4 or abs 5-8 => 3
A workaround: try to eliminate the minus sign.
with sed
x=-12
x=$( sed "s/-//" <<< $x )
echo $x
12
Checking the first character with parameter expansion
x=-12
[[ ${x:0:1} = '-' ]] && x=${x:1} || :
echo $x
12
This syntax is a ternary opeartor. The colon ':' is the do-nothing instruction.
or substitute the '-' sign with nothing (again parameter expansion)
x=-12
echo ${x/-/}
12
Personally, scripting bash appears easier to me when I think string-first.
I translated this solution to bash. I like it more than the accepted string manipulation method or other conditionals because it keeps the abs() process inside the mathematical section
abs_x=$(( x * ((x>0) - (x<0)) ))
x=-3
abs_x= -3 * (0-1) = 3
x=4
abs_x= 4 * (1-0) = 4
For the purist, assuming bash and a relatively recent one (I tested on 4.2 and 5.1):
abs() {
declare -i _value
_value=$1
(( _value < 0 )) && _value=$(( _value * -1 ))
printf "%d\n" $_value
}
If you don't care about the math and only the result matters, you may use
echo $res | awk -F- '{print $NF}'
The simplest solution:
res="${res/#-}"
Deletes only one / occurrence if - is at the first # character.
I was trying to write a BASH loop of the form:
~/$ for i in {1..$(grep -c "match" file)} ; do echo $i ; done
{1..20}
where I was hoping it would produce counted output. So I tried this instead:
~/$ export LOOP_COUNT=$(grep -c "match" file)
~/$ for i in {1..$LOOP_COUNT} ; do echo $i ; done
{1..20}
What I fell back to using was:
~/$ for i in $(seq 1 1 $(grep -c "match" file)) ; do echo $i ; done
1
2
3
...
20
Perfect! But how can I get that behaviour without using seq?
Have you tried this?
max=$(grep -c "match" file)
for (( c=1; c <= $max; c++ ))
do
echo $c
done
According to bash documentation
A sequence expression takes the form {x..y[..incr]}, where x and y are
either integers or single characters, and incr, an optional
increment, is an integer.
You can still use eval in other cases, but Mithrandir's advice is probably faster.
eval "for i in {1..$(grep -c 'match' file)} ; do echo \$i ; done"
Here is a recursive solution:
loop () {
i=$1
n=$2
echo $i
((i < n)) && loop $((i+1)) $n
}
LOOP_COUNT=$(grep -c "Int" sum.scala)
loop 1 $LOOP_COUNT
I want to construct variable name N_foo and N_bar and use their values in the following:
#!/bin/bash
N_foo=2
N_bar=3
for i in { "foo" "bar" }
do
for j in { 1..$(`N_$i`) }
do
echo $j
done
done
I want to use the values of N_foo and N_bar in the two inner loops and print out 1, 2 and 1, 2, 3, respectively. What's the correct syntax?
#!/bin/bash
N_foo=2
N_bar=3
for i in "foo" "bar"
do
key="N_${i}"
eval count='$'$key
for j in `seq 1 $count`
do
echo $j
done
done
You can use the indirect variable reference operator:
Example
var="foo"
nfoo=1
ref=n${var}
echo $ref
echo ${!ref}
Which gives this output:
nfoo
1
#!/bin/bash
N_foo=2
N_bar=3
for i in "foo" "bar"
do
i2="N_$i"
seq 1 ${!i2}
done
I ended up using the following code. It uses the parameter substitution technique (c.f. http://tldp.org/LDP/abs/html/parameter-substitution.html).
#!/bin/bash
N_foo=2
N_bar=3
for i in "foo" "bar"
do
j_max=N_$i
for (( j=1; j<=${!j_max}; j++ ))
do
echo $j
done
done
The ! is history expansion parameter (c.f. http://www.gnu.org/software/bash/manual/bashref.html#Special-Parameters). !j_max will be replaced by the most recent value set to j_max which is N_foo/N_bar in 1st/2nd iteration. It then calls ${N_foo}/${N_bar} which has value 2/3 in the 1st/2nd iteration.
How to pass array as function in shell script?
I written following code:
function test(){
param1 = $1
param2 = $2
for i in ${$param1[#]}
do
for j in ${param2[#]}
do
if($(i) = $(j) )
then
echo $(i)
echo $(j)
fi
done
done
}
but I am getting line 1: ${$(param1)[#]}: bad substitution
There are multiple problems:
you can't have spaces around the = when assigning variables
your if statement has the wrong syntax
array passing isn't right
try not to call your function test because that is a shell command
Here is the fixed version:
myFunction(){
param1=("${!1}")
param2=("${!2}")
for i in ${param1[#]}
do
for j in ${param2[#]}
do
if [ "${i}" == "${j}" ]
then
echo ${i}
echo ${j}
fi
done
done
}
a=(foo bar baz)
b=(foo bar qux)
myFunction a[#] b[#]
You can use the following script accordingly
#!/bin/bash
param[0]=$1
param[1]=$2
function print_array {
array_name=$1
eval echo \${$array_name[*]}
return
}
print_array param
exit 0
A simple way :
function iterate
{
n=${#detective[#]}
for (( i=0; i<n; i++ ))
do
echo ${detective[$i]}
done
}
detective=("Feluda" "Sharlockhomes" "Bomkesh" )
iterate ${detective[#]}