bash order-of-operations in math context: Wrong value assigned - linux

Given the linux shell code,
~$ (( b = a, (a += 3) + $((a = 1)), b++ ))
~$ echo $b
2
Why does $b equal 2? I split the code into three steps:
~$ ((b = a))
~$ (((a += 3) + $((a = 1))))
~$ ((b++))
~$ echo $b
1
$b equals 1 this time, why?
P.S. Neither a nor b is initialized.

Your two examples are not equivalent. The arithmetic expansion $((...)) is performed before the (( ... )) statement is evaluated, so the following are equivalent:
(( b = a, (a += 3) + $((a = 1)), b++ ))
and
a=1
(( b = a, (a += 3) + a, b++ ))
Your attempt at breaking it into three parts is equivalent to
((b = a)) # b = 0 since uninitialized a is treated as 0
a=1
(((a += 3) + a))
((b++)) # b = 1
The difference is when, in the sequence of evaluation, a is first assigned the value of 1.

Because you set b = a, then bash waits for last assigning of a. In b will be assigned same value as the value assigned to a.
~$ (( b = a, (a += 3) + $((a = 5)), b++ ))
~$ echo $b
6
~$ echo $a
8
EDIT
1) column-separated expressions are treated sequentially
~$ echo $((1+1, 2+2, 3+3))
6
2) $((...)) expressions are treated first
which gives result:
(( b = a, (a += 3) + $((a = 1)), b++ ))
$((a = 1)) #a=1
b = a #a=b=1
a += 3 #a=4, b=1
b++ #a=4, b=2

Related

Does a second (inner) loop open a subprocess?

I have come to an interesting topic, while I was experimenting
recursive loops in a shell script.
First I came across interesting functionality of
for x in a b c d; do
for x in e f d h; do
a=test
done
echo $x
done
Always outputting letter h as variable $x. Which makes sense, as inner loop uses x as variable name too, and h would be the last one picked in such loop.
My concern is, does inner loops open a subprocess or how is the functionality guaranteed?
for x in a b c d; do
for x in e f d h; do
pstree $$
done
pstree $$
done
.. suggest in the output that no sub-processes are opened on nested loops.
I am probably a little bit stuck in a loop, but is there a good documentation how a shell operates loops? I would like to know how does shell interpret the loops so it's kept within one layer of instructions.
Modify your example in this way:
#! /bin/bash
set -x
for x in a b; do
: x=$x
for x in c d; do
: x=$x
a=test
done
echo $x
done
This outputs:
+ for x in a b
+ : x=a
+ for x in c d
+ : x=c
+ a=test
+ for x in c d
+ : x=d
+ a=test
+ echo d
d
+ for x in a b
+ : x=b
+ for x in c d
+ : x=c
+ a=test
+ for x in c d
+ : x=d
+ a=test
+ echo d
d
Now you can see, the inner loop modifies x after the outer loop. When you print x it has always the last value of the inner loop.

How to convert result as Integer in bash

when I do
$ ls | wc -l
703
It gave me the result 703, I want to print 702 (703-1)
How can I do it in bash?
You can use arithmetic expansion:
result=$(( $(ls | wc - l) - 1))
or just ignore one of the files
result=$(ls | tail -n+2 | wc -l)
Note that it doesn't work if filenames contain the newline character; use ls -q to get one filename per line in such a case. This applies to the first solution, too, if you're interested in the number of files and not the number of lines in their names.
(Cheeky answer) Remove one line from the output before counting :D
ls | sed '1d' | wc -l
How to convert result as Integer in bash
#choroba has already answered this question and it should have solved OP's problem. However, I want to add more to his answer.
The OP's wants to convert the result into Integer but Bash doesn't have any data type like Integer.
Unlike many other programming languages, Bash does not segregate its variables by "type." Essentially, Bash variables are character strings, but, depending on context, Bash permits arithmetic operations and comparisons on variables. The determining factor is whether the value of a variable contains only digits.
See this for arithmetic operation in Bash.
See this for a best example to learn the untyped nature of Bash. I have posted the example below:
#!/bin/bash
# int-or-string.sh
a=2334 # Integer.
let "a += 1"
echo "a = $a " # a = 2335
echo # Integer, still.
b=${a/23/BB} # Substitute "BB" for "23".
# This transforms $b into a string.
echo "b = $b" # b = BB35
declare -i b # Declaring it an integer doesn't help.
echo "b = $b" # b = BB35
let "b += 1" # BB35 + 1
echo "b = $b" # b = 1
echo # Bash sets the "integer value" of a string to 0.
c=BB34
echo "c = $c" # c = BB34
d=${c/BB/23} # Substitute "23" for "BB".
# This makes $d an integer.
echo "d = $d" # d = 2334
let "d += 1" # 2334 + 1
echo "d = $d" # d = 2335
echo
# What about null variables?
e='' # ... Or e="" ... Or e=
echo "e = $e" # e =
let "e += 1" # Arithmetic operations allowed on a null variable?
echo "e = $e" # e = 1
echo # Null variable transformed into an integer.
# What about undeclared variables?
echo "f = $f" # f =
let "f += 1" # Arithmetic operations allowed?
echo "f = $f" # f = 1
echo # Undeclared variable transformed into an integer.
#
# However ...
let "f /= $undecl_var" # Divide by zero?
# let: f /= : syntax error: operand expected (error token is " ")
# Syntax error! Variable $undecl_var is not set to zero here!
#
# But still ...
let "f /= 0"
# let: f /= 0: division by 0 (error token is "0")
# Expected behavior.
# Bash (usually) sets the "integer value" of null to zero
#+ when performing an arithmetic operation.
# But, don't try this at home, folks!
# It's undocumented and probably non-portable behavior.
# Conclusion: Variables in Bash are untyped,
#+ with all attendant consequences.
exit $?

Shell for loop, stopping at declaration

I'm trying to write a for loop that goes from 1 to 10, then calculates ( 1 through 10 mod 5) + 2. After that I want to display it like this (1 to 10 mod 5) + 2 = answer. However i'm getting an error at the beginning of the loop which is a syntax error.
for (( i = 0; i <= 10; i++)); do
calculate=(i % 5) + 2
echo ("("i "% 5) + 2" calculate)
done
Try these changes:
calculate=$(( i % 5 + 2 ))
# $(( ... )) is the shell's way to do arithmetic
echo "($i % 5) + 2 = " $calculate
# $x is a way to refer to the value of variable x
# (also inside a double-quoted string)
The for loop header is actually OK.

How to add a single special character randomly anywhere inside a string? Bash

I have this script that generates a random character between 8-16. I am confused as to how I would add a single random special character from a bank [! # # $ % ^ & * ( ) _ + ] anywhere randomly inside this string?
if [ $# -eq 0 ] then
pwdlen=$(((RANDOM % 9 ) +8))
spclen=$((RANDOM % 1))
char=(0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V X W Y Z)
chars=(~ ! # # $ % ^ & * - +)
#rand2=$random % 11
max=${#char[*]}
for i in `seq 1 $pwdlen`
do
let "rand=$RANDOM % $max"
str="${str}${char[$rand]}"
done
echo $str
exit 0
fi
teststring=foobarspam
specialchars='!##$%^&*()_+'
randomchar=${specialchars:RANDOM % ${#specialchars}:1}
randompos=$(( RANDOM % ( ${#teststring} + 1 ) ))
newstring=${teststring:0:randompos}${randomchar}${teststring:randompos}
You can use the following code.
#!/bin/bash
if [ $# -eq 0 ]; then
pwdlen=$(((RANDOM % 9 ) +8))
spclen=$((RANDOM % 1))
char=(0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V X W Y Z)
chars=('~' '!' '#' '#' '$' '%' '^' '&' '*' '-' '+')
#rand2=$random % 11
max=${#char[*]}
for i in `seq 1 $pwdlen`
do
let "rand=$RANDOM % $max"
str="${str}${char[$rand]}"
done
A=$(echo $str|wc -c) ## To get the count of
P=$((RANDOM % $A)) ## To get a random position of where to insert the character.
I=$((RANDOM % 11)) ## To get a random index number of chars array
C=${chars[$#]}
echo $str | sed 's!^\(.\{'$P'\}\).!\1\'"$C"'!' ## Inserting the special character to string in defined position
exit 0
fi
Output:
$ for i in `seq 1 10`;do ./test1;done
j^eh8BmD2H
0B01^1AN6EVw
Wu2$LLTILuDN8fSV
e^90gmHjksDo
eB7wa\#fmwf
NVAtJkmfqx~
JaHvD%uyO3rB
ncFrgyyz~UkZ
q0LLRHUNATM8DL
X%ARcXgyC1Do
I am not sure what script language are you using. I wrote a solution for you using PHP. If PHP is not what you are using, you should be able to convert the same logic to other languages and get the same results.
<?php
//This is the original string where you want to add a random character to
$org_string = 'This is My original String';
//calculates the length of the string
$org_length = strlen($org_string);
//find a random position
$pos = rand(0, $org_length-1);
//concatenate the first part of the string, random character, the remaining string
$final = substr($org_string, 0, $pos) . getOne() . substr($org_string, $pos);
//print the final value
echo $final;
//return a random string
function getOne(){
//the following string is 12 characters in length. it is all available characters that you want to select from
$str = '!##$%^&*()_+';
//return a random character
return $str[rand(0, 11)];
}
?>

Variable as bash array index?

#!/bin/bash
set -x
array_counter=0
array_value=1
array=(0 0 0)
for number in ${array[#]}
do
array[$array_counter]="$array_value"
array_counter=$(($array_counter + 1))
done
When running above script I get the following debug output:
+ array_counter=0
+ array_value=1
+ array=(0 0 0)
+ for number in '${array[#]}'
+ array[$array_counter]=1
+ array_counter=1
+ for number in '${array[#]}'
+ array[$array_counter]=1
+ array_counter=2
+ for number in '${array[#]}'
+ array[$array_counter]=1
+ array_counter=3
Why does the variable $array_counter not expand when used as index in array[]?
Bash seems perfectly happy with variables as array indexes:
$ array=(a b c)
$ arrayindex=2
$ echo ${array[$arrayindex]}
c
$ array[$arrayindex]=MONKEY
$ echo ${array[$arrayindex]}
MONKEY
Your example actually works.
echo ${array[#]}
confirms this.
You might try more efficient way of incrementing your index:
((array_counter++))

Resources