Reversing a bash for loop - linux

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.

Related

How to find the location(s) of specific characters in a string

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.

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

Bash initialize sparse array

I have an array, index is the hard drive size and value is number of hard drives having same size. So this is what i do.
DRIVE_SIZES[$DRIVE_SIZE]=`expr ${DRIVE_SIZES[$DRIVE_SIZE]} + 1`
I have not initialized the DRIVE_SIZES array to 0. So above line might not work.
I would like to initialize a sparse array in bash script.
Lets say all drives in the host are of the same size, except one. Some 10 drives are of size 468851544 and one drive is of size 268851544. So I cannot initialize all index from 0-468851544 because I dont know the maximum disk size beforehand.
So is there a way to initialize such an sparse array to 0. May be if there is way to declare an integer array in bash that might help. But after some initial research found out I can declare an integer, but not integer array(might be wrong on this). Can someone help me with this ?
I read this, but this might not be the solution to me
Use increment in an arithmetic expression:
(( ++DRIVE_SIZES[DRIVE_SIZE] ))
You can use parameter substitution to put a zero into the expression when the array key has not been defined yet:
DRIVE_SIZES[$DRIVE_SIZE]=`expr ${DRIVE_SIZES[$DRIVE_SIZE]:-0} + 1`
N.B. this is untested, but should be possible.
An array element when unset and used in arithmetic expressions inside (( )) and $(( )) has a default value of 0 so an expression like this would work:
(( DRIVE_SIZES[DRIVE_SIZE] = DRIVE_SIZES[DRIVE_SIZE] + 1 ))
Or
(( DRIVE_SIZES[DRIVE_SIZE] += 1 ))
Or
(( ++DRIVE_SIZES[DRIVE_SIZE] ))
However when used outside (( )) or $(( )), it would still expand to an empty message:
echo ${DRIVE_SIZES[RANDOM]} shows "" if RANDOM turns out to be an index of an element that is unset.
You can however use $(( )) always to get the proper presentation:
echo "$(( DRIVE_SIZES[RANDOM] ))" would return either 0 or the value of an existing element, but not an empty string.
Using -i to declare, typeset or local when declaring arrays or simple variables might also help since it would only allow those parameters to have integral values, and the assignment is always done as if it's being assigned inside (( )) or $(( )).
declare -i VAR
A='1 + 2'
is the same as (( A = 1 + 2 )).
And with (( B = 1 )), A='B + 1' sets A to 2.
For arrays, you can set them as integral types with -a:
declare -a -i ARRAYVAR
So a solution is to just use an empty array variable and that would be enough:
ARRAYVAR=()
Or
declare -a -i ARRAYVAR=()
Just make sure you always use it inside (( )) or $(( )).

Problem with bash code

function dec_to_bin {
if [ $# != 2 ]
then
return -1
else
declare -a ARRAY[30]
declare -i INDEX=0
declare -i TEMP=$2
declare -i TEMP2=0
while [ $TEMP -gt 0 ]
do
TEMP2="$TEMP%2"
#printf "%d" "$TEMP2"
ARRAY[$INDEX]=$TEMP2
TEMP=$TEMP/2
INDEX=$[ $INDEX + 1 ] #note
done
for (( COUNT=INDEX; COUNT>-1; COUNT--)){
printf "%d" "${ARRAY[$COUNT]}" <<LINE 27
#echo -n ${ARRAY[$COUNT]} <<LINE 28
}
fi
}
why is this code giving this error
q5.sh: line 27: ARRAY[$COUNT]: unbound variable
same error comes with line 28 if uncommented
One more question, I am confused with the difference b/w '' and "" used in bash scripting any link to some nice article will be helpfull.
It works fine for me except that you can't do return -1. The usual error value is 1.
The error message is because you have set -u and you're starting your for loop at INDEX instead of INDEX-1 (${ARRAY[INDEX]} will always be empty because of the way your while loop is written). Since you're using %d in your printf statement, empty variables will print as "0" (if set -u is not in effect).
Also, it's meaningless to declare an array with a size. Arrays in Bash are completely dynamic.
I would code the for loop with a test for 0 (because the -1 looks confusing since it can't be the index of an numerically indexed array):
for (( COUNT=INDEX - 1; COUNT>=0; COUNT--))
This form is deprecated:
INDEX=$[ $INDEX + 1 ]
Use this instead:
INDEX=$(( $INDEX + 1 ))
or this:
((INDEX++))
I also recommend using lower case or mixed case variables as a habit to reduce the chance of variable name collision with shell variables.
You're not using $1 for anything.

Compare integer in bash, unary operator expected

The following code gives
[: -ge: unary operator expected
when
i=0
if [ $i -ge 2 ]
then
#some code
fi
why?
Your problem arises from the fact that $i has a blank value when your statement fails. Always quote your variables when performing comparisons if there is the slightest chance that one of them may be empty, e.g.:
if [ "$i" -ge 2 ] ; then
...
fi
This is because of how the shell treats variables. Assume the original example,
if [ $i -ge 2 ] ; then ...
The first thing that the shell does when executing that particular line of code is substitute the value of $i, just like your favorite editor's search & replace function would. So assume that $i is empty or, even more illustrative, assume that $i is a bunch of spaces! The shell will replace $i as follows:
if [ -ge 2 ] ; then ...
Now that variable substitutions are done, the shell proceeds with the comparison and.... fails because it cannot see anything intelligible to the left of -gt. However, quoting $i:
if [ "$i" -ge 2 ] ; then ...
becomes:
if [ " " -ge 2 ] ; then ...
The shell now sees the double-quotes, and knows that you are actually comparing four blanks to 2 and will skip the if.
You also have the option of specifying a default value for $i if $i is blank, as follows:
if [ "${i:-0}" -ge 2 ] ; then ...
This will substitute the value 0 instead of $i is $i is undefined. I still maintain the quotes because, again, if $i is a bunch of blanks then it does not count as undefined, it will not be replaced with 0, and you will run into the problem once again.
Please read this when you have the time. The shell is treated like a black box by many, but it operates with very few and very simple rules - once you are aware of what those rules are (one of them being how variables work in the shell, as explained above) the shell will have no more secrets for you.
Judging from the error message the value of i was the empty string when you executed it, not 0.
I need to add my 5 cents. I see everybody use [ or [[, but it worth to mention that they are not part of if syntax.
For arithmetic comparisons, use ((...)) instead.
((...)) is an arithmetic command, which returns an exit status of 0 if
the expression is nonzero, or 1 if the expression is zero. Also used
as a synonym for "let", if side effects (assignments) are needed.
See: ArithmeticExpression
Your piece of script works just great. Are you sure you are not assigning anything else before the if to "i"?
A common mistake is also not to leave a space after and before the square brackets.

Resources