What does the '${var// /+}' mean in shell script? - linux

I have never seen the following shell script syntax:
cpu_now=($(head -n 1 /proc/stat))
cpu_sum="${cpu_now[#]:1}"
cpu_sum=$((${cpu_sum// /+}))
Can anyone explain what the ${cpu_sum// /+} mean here?

It means the same as $cpu_sum, but with all occurrences of  (a space) being replaced by +. (See ยง3.5.3 "Shell Parameter Expansion" in the Bash Reference Manual.)

cpu_sum=$((${cpu_sum// /+}))
It is actually 2 step operation:
First all the spaces are being replaced by + in ${cpu_sum// /+}
Then using $((...)) arithmetic addition is being performed for adding all the numbers in $cpu_sum variable to get you aggregate sum.
Example:
# sample value of cpu_sum
cpu_sum="3222 0 7526 168868219 1025 1 357 0 0 0"
# all spaced replaced by +
echo ${cpu_sum// /+}
3222+0+7526+168868219+1025+1+357+0+0+0
# summing up al the values and getting aggregate total
echo $((${cpu_sum// /+}))
168880350

Related

what is the difference between $(($RANDOM % 10 +1)) and $(( ( RANDOM % 10 ) + 1 ))

So I'm trying to create a random number limit the range from 1 to 10. There is a slightly different syntax between the two and I don't know if there any difference between those.
$(($RANDOM % 10 +1))
I tried this and it's working fine.
$(( ( RANDOM % 10 ) + 1 )). Including an extra () between RANDOM % 10 and +1 seems to work the same as the code above. But it has only one $ instead of 2.
Nothing. % has higher precedence (the same as *) than +, so the unparenthesized version is equivalent to the explicitly parenthesized one.
I originally missed that the second one also used RANDOM instead of $RANDOM. In an arithmetic context, a string is treated as an identifier, and is (recursively) expanded until you get an integer. If at any point the string is not a defined parameter, the value 0 is used instead. For example:
$ foo=bar
$ bar=6
$ echo $((bar))
6
$ echo $((foo))
6
In the last case, $foo expands to bar, which expands to 6.
IMO, it's better to use the explicit expansion. If the parameter isn't set due to a typo, you'll get an explicit error
$ foo=6
$ echo $((10 + $fo))
bash: 10 + : syntax error: operand expected (error token is "+ ")
rather than a silent "success" that might not be intended
$ echo $((10 + fo))
10 # ?? Why not 16?

What is meant by `&` and `#` in shell/bash coding? Please decode the line of Bash script

I am new to bash programming. I am trying to understand the following code.
I know only that $ is used to pass the parameters.
function check_data () {
local pattern=$1 iterations=$2 tck=$3 tdi=$4 tdo=$5
...
}
pattern="0110011101001101101000010111001001"
pins=(2 3 4 17 27 22)
# tck, tdi, and tdo are integer number
checkdatret=$(check_data $pattern $((2*${#pattern})) ${pins[$tck]} ${pins[$tdi]} ${pins[$tdo]})
Please, can you write the flow of execution steps of the last line of code?
We will see how the $,# works, and then answer the question.
Part 1
${#pattern} will give the length of the string that pattern holds.
Therefore, in your above example
user#host:~$ echo ${#pattern}
34
since 34 is the length of the string 0110011101001101101000010111001001.
Part 2
${pins[$tck]} is used to get the value at index tck of array pins. If the variable is not set, then it will default to 0. Here, since tck is not set,
${pins[$tck]} is equivalent to ${pins[0]}. Thus the output will be:
user#host:~$ echo ${pins[$tck]}
2
The output will be 2 since array indexing starts at 0 in bash.
Part 3
$() is used for command substitution in bash. The command inside () will be executed and the output will be substituted.
Original question
$((2*${#pattern})) evalutes to $((2*34)) which evalutes to 68
Since tck,tdi and tdo are not set, it will default to 0.
Therefore,
${pins[$tck]} evaluates to ${pins[0]} which evalutes to 2, the first element in the pins array.
Similarly, ${pins[$tdi]} and ${pins[$tdo]} both evalutes to 2
Therefore the final line in your script is now,
checkdatret=$(check_data 0110011101001101101000010111001001 68 2 2 2)
Now the function will be called with parameters
$1=0110011101001101101000010111001001
$2=68
$3=2
$4=2
$5=2
You need to echo the result from check_data function which will be substituted as
checkdatret=YOUR_RESULT_FROM_FUNC
Try to analyze the code step by step by inserting some output commands
echo $pattern
echo ${#pattern}
echo $((2*${#pattern}))
echo $tck
echo ${pins[$tck]}
# ...
Without seeing what you're using as input to the script or the whole thing it's quite difficult to give an accurate description.
However:
# Var = how many characters in the variable
${#pattern} # 34 characters
echo ${#pattern}
34
I don't see any "&"'s in your code?
check_data is a function call, and everything else are arguments provided to check_data
# This is an array and $tck is supposed to represent a "Key"
${pins[$tck]}
# You can list keys with ${pins[#]}
Paste your code into shellcheck.net
function check_data () {
local pattern=$1 iterations=$2 tck=$3 tdi=$4 tdo=$5
...
}
Don't use both function and (). Prefer the ().
check_data() {
local pattern=$1 iterations=$2 tck=$3 tdi=$4 tdo=$5
...
}
local defines the variables in the function as scoped to that function. $1 and such are the values of the positional parameters the function receives.
pattern="0110011101001101101000010111001001"
That's just a simple string.
pins=(2 3 4 17 27 22)
This assigned the listed integers as values in a standard array. ${pins[0] is 2, ${pins[1] is 3, and so on to a ${pins[5] of 22.
checkdatret=$(...)
Executes a subshell and assigns the stdout from it to $checkdatret.
check_data val1 val2 val3 val4 val5
Executes the function, passign the evaluated value of each argument, so -
check_data $pattern $((2*${#pattern})) ${pins[$tck]} ${pins[$tdi]} ${pins[$tdo]}
passes in 0110011101001101101000010111001001 (the value of the global variable $pattern) into the function as $1, which gets assigned to the local variable $pattern.
$2 gets 68 to assign into iterations, because ${#pattern} is the length of $pattern, which is 34; $(( ... )) gets replaces with the arithmetic evaluation of its content, which is 2*${#pattern}.
I must assume tck, tdi, and tdo also have global versions, as the local ones inside the function would be unavailable here. I must also assume that each has a value in the range of 0-5, as each is used as an index to reference one of the six values in pins.
It would likely help to see the rest of the code and know what it is you actually need.

pbsnodes and bash string matching

I have a string of the form: "8, 14-24, 30-45, 9", which is a substring of the output of pbsnodes. This shows the cores in use on a given node, where 14-24 is a range of cores in use.
I'd like to know the total number of cores in use from this string, i.e.
1 + (24 - 14 + 1) + (45 - 30 + 1 )+ 1 in this example, using a bash script.
Any suggestions or help is much appreciated.
Michael
You could use pure bash techniques to achieve this. By reading the string to array and doing the arithmetic operator using the $((..)) operator. You can run these commands directly on the command-line,
IFS=", " read -ra numArray <<<"8, 14-24, 30-45, 9"
unset count
for word in "${numArray[#]}"; do
(( ${#word} == 1 )) && ((++count)) || count=$(( count + ${word#*-} - ${word%-*} + 1 ))
done
printf "%s\n" "$count"
The idea is
The read with -a splits the string on the de-limiter set by IFS and reads it into the array numArray
A loop on each of the elements, for each element, if the element is just single character, just increment total count by 1
For numeric ranges, do manipulation as e.g. for number a-b use parameter expansion syntax ${word#*-} and ${word%-*} to extract b and a respectively and do b-a+1 and add it with count calculated already and print the element after the loop
You can put this in a bash script with she-bang set to #!/bin/bash and run the script or run it directly from the command-line

Bash for loop parameter unexpected behaviour [duplicate]

This question already has answers here:
Variables in bash seq replacement ({1..10}) [duplicate]
(7 answers)
Brace expansion with a Bash variable - {0..$foo}
(5 answers)
Closed 8 years ago.
I'm making a program in bash that creates a histoplot, using numbers I have created. The numbers are stored as such (where the 1st number is how many words are on a line of a file, and the 2nd number is how many times this amount of words on a line comes up, in a given file.)
1 1
2 4
3 1
4 2
this should produce:
1 #
2 ####
3 #
4 ##
BUT the output I'm getting is:
1 #
2 #
3 #
4 #
however the for loop is not recognising that my variable "hashNo" is a number.
#!/bin/bash
if [ -e $f ] ; then
while read line
do
lineAmnt=${line% *}
hashNo=${line##* }
#VVVV Problem is this line here VVVV
for i in {1..$hashNo}
#This line ^^^^^^^ the {1..$hashNo}
do
hashes+="#"
done
printf "%4s" $lineAmnt
printf " $hashes\n"
hashes=""
done < $1
fi
the code works if I replace hashNo with a number (eg 4 makes 4 hashes in my output) but it needs to be able to change with each line (no all lines on a file will have the same amount of chars in them.
thanks for any help :D
A sequence expression in bash must be formed from either integers or characters, no parameter substitutions take place before hand. That's because, as per the bash doco:
The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.
In other words, brace expansion (which includes the sequence expression form) happens first.
In any case, this cries out to be done as a function so that it can be done easily from anywhere, and also made more efficient:
#!/bin/bash
hashes() {
sz=$1
while [[ $sz -ge 10 ]]; do
printf "##########"
((sz -= 10))
done
while [[ $sz -gt 0 ]]; do
printf "#"
((sz--))
done
}
echo 1 "$(hashes 1)"
echo 2 "$(hashes 4)"
echo 3 "$(hashes 1)"
echo 4 "$(hashes 2)"
which outputs, as desired:
1 #
2 ####
3 #
4 ##
The use of the first loop (doing ten hashes at a time) will almost certainly be more efficient than adding one character at a time and you can, if you wish, do a size-50 loop before that for even more efficiencies if your values can be larger.
I tried this for (( i=1; i<=$hashNo; i++ )) for the for loop, it seems to be working
Your loop should be
for ((i=0; i<hashNo; i++))
do
hashes+="#"
done
Also you can stick with your loop by the use of eval and command substitution $()
for i in $(eval echo {1..$hashNo})
do
hashes+="#"
done

Why is "echo [#10]" equal to 1?

Can someone explain why these echo commands doesn't output [#10] and so on?
# echo [#10]
1
# echo [#11]
1
# echo [#12]
1 2
# echo [#13]
1
# echo [#14]
1
You have a file named "1" and a file named "2" in your current directory.
The shell is performing pattern matching on the glob patterns before handing the results to echo. [#10] is a character class containing a #, a 1 and a 0.
See http://www.gnu.org/software/bash/manual/bashref.html#Pattern-Matching
If you want the literal [#10], etc, you have to enclose it in quotes, single or double doesn't matter.
(to answer the question in your last comment)
You could use the printf(1) command:
printf "Error: %s went wrong. Error code [#%d]\n" "something" $[10+2]
The $[10+2] is here to show how to do arithmetic in shell. You could replace "something" with e.g. $somevariable ...

Resources