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
Related
With the aid of this question, I can find out if a string holds a specific character. I want to be able to find out where the character actually is. For example for the string banana, how would I be able to determine the letter n is the 3rd and 5th letter, or for the letter a is the 2nd,4th and 6th letter. and b is the first letter.
Q: For a given string, how can I find the location of a given character in that string?
You can do it with a for loop.
char=a
string=banana
len=${#string}
for (( i=0; i < len; i++ )); do
if [[ $char == ${string:$i:1} ]]
then echo $i
fi
done
The positions printed are zero-based. You could echo $((i+1)) to get 1-based positions instead.
${string:$i:1} extracts the ith character of the string, using bash's substring operator, as explained in Shell Parameter Expansion:
${parameter:offset:length}
This is referred to as Substring Expansion. It expands to up to length characters of the value of parameter starting at the character specified by offset.
Here's a fancy way to do it:
#!/usr/bin/env bash
findChar(){
string="${1}"
char="${2}"
length=${#string}
offset=0
r=()
while true; do
string="${string#*${char}}"
length_new="${#string}"
if [[ "${length}" == "${length_new}" ]]; then
echo "${r[#]}"
return
fi
offset=($(( $offset + $length - $length_new )))
r+=("${offset}")
length="${length_new}"
done
}
findChar banana b
findChar banana a
Here's my take on this:
#!/usr/bin/env bash
[[ ${BASH_VERSINFO[0]} < 4 ]] && { echo "Requires bash 4."; exit 1; }
string="${1:-banana}"
declare -A result=()
for ((i=0; i<${#string}; i++)); do
result[${string:$i:1}]="${result[${string:$i:1}]} $i"
done
declare -p result
The idea is that we walk through the string, adding character positions to strings that are values in an array whose subscripts are the letters you're interested in. It's quick & easy, and gives you a result set you can manipulate afterwards, rather than just sending things to stdout.
My result with this is:
$ ./foo
declare -A result='([a]=" 1 3 5" [b]=" 0" [n]=" 2 4" )'
$ ./foo barber
declare -A result='([a]=" 1" [b]=" 0 3" [e]=" 4" [r]=" 2 5" )'
Results are zero-based (i.e. "b" is in position 0).
Note an interesting side-effect of this method is that every position is preceded by a space, so if you want to count the number of occurrences of a character, you can just count the spaces:
$ declare -A result
$ result[a]=" 1 3 5"
$ count="${result[a]//[0-9]/}"
$ echo "${#count}"
3
$
I don't know what you're planning to do with this data, but if you like, you could easily turn these string results into arrays of their own for easier handling within bash.
Note that associative arrays were introduced with bash version 4.
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
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
I have this:
for (( count= "$WP_RANGE_START"; count< "$WP_RANGE_STOP"+1; count=count+1 ));
Where WP_RANGE_STARTis a number like 1 and WP_RANGE_STOPis a number like 10.
Right now this will step though going 1,2,...10
How can I do so that it counts backwards?(10,9,...1)
I guess the mirror image of what you have would be
for (( count="$WP_RANGE_STOP"; count >= "$WP_RANGE_START"; count=count-1 ));
But a less cumbersome way to write it would be
for (( count=WP_RANGE_STOP; count >= WP_RANGE_START; count-- ));
The $ is unnecessary in arithmetic context.
When dealing with literals, bash has a range expansion feature using brace expansion:
for i in {0..10}; # or {10..0} or what have you
But it's cumbersome to use with variables, as the brace expansion happens before parameter expansion. It's usually easier to use arithmetic for loops in those cases.
Your incrementing code can be "simplified" as:
for count in $(eval echo {$WP_RANGE_START..$WP_RANGE_STOP});
So, to decrement you can just reverse the parameters"
for count in $(eval echo {$WP_RANGE_STOP..$WP_RANGE_START});
Assuming you've got a bash version of 3 or higher, you can specify an increment or decrement by appending it to the range, like so:
CHANGE=1
for count in $(eval echo {$WP_RANGE_STOP..$WP_RANGE_START..$CHANGE});
The for loop is your problem.
i=11 ; until [ $((i=i-1)) -lt 1 ] ; do echo $i ; done
OUTPUT
10
9
8
7
6
5
4
3
2
1
You don't need any bashisms at all.
I want to generate a series of files in which the file name of each file shall be increased by 1 (File1.txt, File2.txt, File3.txt, ... FileN.txt) where N = 250
Each file has 2 lines.
AAAXXX (where XXX = 001 to 250 - automatic increased for each file)
BBBYYY (where YYY = 3 digit random number )
Example:
File1.txt:
AAA001
BBB175
File5.txt:
AAA005
BBB067
File102.txt:
AAA102
BBB765
I'm a newbie using Ubuntu Linux 12.04 - but I'm hoping someone can assist.
You can do it as follows:
#!/bin/bash
for i in {1..250}
do
printf "AAA%03d\nBBB%03d" ${i} $(($RANDOM % 1000)) > File${i}.txt
done
Explanation:
for i in {1..250} - bash way of specifying iteration from 1 to 250, increment size of 1.
printf - shell printf command - used to print formatted string
AAA - string literal (means "exactly as written")
%03d - formatted string, this prints a decimal number padded with 3 zero's in front.
\n - newline
BBB - another string literal
%03d - same as before
${i} - this is the value used in the first formatted string (%03d)
$(($RANDOM % 1000)) - $RANDOM is a system variable that provides a random number for you each time you access it. The % 1000 to take the modulo so you get a range betwee 0-999. This is used in the 2nd formatted string (%03d)
> File${i}.txt: output redirection; creates and saves to a file (overwrites if file already exists.
Here's a quick one-liner that might start you off:
for i in {1..250}; do printf "AAA%03d\nBBB%03d" $i $(($RANDOM % 1000)) > "File${i}.txt"; done
Using bash:
for i in {1..250}; do printf "AAA%03d\nBBB%03d\n" "$i" "$((RANDOM%1000))" > "File$i.txt"; done
You can write a bash script for this
#!/bin/bash
for (( i=1; i<=250; i++ ))
do
NUMBER=$[ ( $RANDOM % 999 ) + 100 ]
echo "AAA$i BBB$NUMBER" > File$i.txt
done