We all know arrays in Bash are indexed from zero, and in zsh are indexed from one.
How can the script know it should use 0 or 1 if I can't ensure the running environment is bash, zsh or something else?
Expected code sample:
#!/bin/sh
detect_array_start_index(){
# ... how?
echo 1
}
ARR=(ele1 ele2)
startIndex=$(detect_array_start_index) # 0 or 1
for (( i=${startIndex}; i < ${#ARR[#]} + $startIndex; i++ )); do
echo "$i is ${ARR[$i]}"
done
I have a idea is find the index of the first value in a fixed array, I got this: Get the index of a value in a Bash array, but the accepted answer use bash variable indirection syntax ${!VAR[#]}, which is invalid in zsh.
Check the index 1 element of a two element array:
detect_array_start_index() {
local x=(1 0)
echo ${x[1]}
}
You can set the KSH_ARRAYS option in zsh to force array indexing to start at 0.
{ setopt KSH_ARRAYS || : ; } 2> /dev/null
ARR=(ele1 ele2)
for ((i=0; i < ${#ARR[#]}; i++ )); do
echo "$i is ${ARR[$i]}"
done
The command group and redirection allows the entire command to act as a no-op if executed from bash. The preceding code produces the same output in zsh and bash.
Related
I am trying to write a custom script to monitor the disk usage space of "n" number of servers. I have two arrays, one array consists of the actual usage and the other array consists of the allowed limit. I would like to loop through the used storage array; determine the percentage, round it off to the nearest integer and output the same on the console to be later saved in an array.
I have the following piece of code that does this:
readarray -t percentage_storage_limit <<< "$(for ((j=0; j < ${#storage_usage_array[#]}; j++));
do $(awk "BEGIN {
ac=100*${storage_usage_array[$j]}/${storage_limit_array[$j]};
i=int(ac);
print (ac-i<0.5)?i:i+1
}");
done)";
The length of both storage_usage_array and storage_limit_array are the same. An index in storage_usage_array corresponds to the storage used on a server and an index on storage_limit_array corresponds to the limit on the same server.
Although the above statement runs as expected, I see a "command not found error" as follow, which is causing these output to not be saved in the "percentage_storage_limit" array.
8: command not found
4: command not found
am I missing something here? Any help would be really appreciated.
I think you getting over-complicated syntax-wise. I would just accumulate the array within the for loop
percentage_storage_limit=()
for ((j=0; j < ${#storage_usage_array[#]}; j++)); do
percentage_storage_limit+=( $(
awk -v u="${storage_usage_array[$j]}" -v l="${storage_limit_array[$j]}" '
BEGIN {
ac = 100 * u / l
i = int(ac)
print (ac-i < 0.5) ? i : i+1
}
'
) )
done
The reason it doesn't work, is that whan you enclose awk in $(...) you tell bash to execute it's output, thus you want to execute 8 or 4 and bash errors to you that it didn't find such command. Just don't enclose awk in $(...), you want to capture it's output, not execute it's output. And it would be better to use < <(...) then <<<"$(...)":
readarray -t percentage_storage_limit < <(
for ((j=0; j < ${#storage_usage_array[#]}; j++)); do
awk "BEGIN {
ac=100*${storage_usage_array[$j]}/${storage_limit_array[$j]};
i=int(ac);
print (ac-i<0.5)?i:i+1
}";
done
)
Anyway Glenn's answer shows the 'good' way to do this, without readarray call.
I looked everybody but I'm stuck with this code.
For example :
The user calls ./array.sh 3 5 6 2 1
I am supposed to sort (i thought bubble sort) and print the array sorted.
#! /bin/bash
tab=( $# )
define -i temp
for ((i = ${#tab[*]-1 ; i >= 0 ; i--)) ; do
for ((j = 0 ; i - 1; j++)) ; do
if [ ${tab[$i]} > ${tab[$i+1]} ] then
$temp = ${tab[$i+1]
${tab[$i+1]} = ${tab[$i]}
${$tab[$i]} = $temp
fi
done
echo ${tab[*]} #print the array
But bash is not happy with that, he keeps tellin me that I cannot assing values like that.
What do I do wrong ? Can you help me please ? I looked in a lot of places but there is no way to find the solution.
Thanks you in advance guys.
You cannot assign a value to a value (e.g. ${tab[$i+1]} = ${tab[$i]}); you must assign to a name or to an array element. Also, you may not have space on either side of the equals sign (=) in a bash assignment, except in numeric context.
And you dropped some closing braces.
And you misspelled "declare".
And you need a semicolon or newline between your if condition and your then.
And you didn't terminate your inner loop.
And you reference a non-existent array element via ${tab[$i+1]}.
And your inner loop has a constant as its termination condition.
And ${#tab[*]-1} incorrectly attempts to do math inside the braces delimiting the variable reference.
And you referenced the wrong index variable (consistently) in your inner loop.
And > is a redirection operator, not greater than, except in numeric context.
Once you clear up that multitude of errors, you end up with
#! /bin/bash
tab=( $# )
declare -i temp
for ((i = ${#tab[*]} - 1; i > 0 ; i--)) ; do
for ((j = 0 ; $j < $i; j++)) ; do
if [ ${tab[$j]} -gt ${tab[$j+1]} ]; then
temp=${tab[$j+1]}
tab[$j+1]=${tab[$j]}
tab[$j]=$temp
fi
done
done
echo ${tab[*]} #print the array
which actually works.
Your variable assignment syntax is all wrong.
First, you don't put $ before the variable being assigned.
Second, you must not have spaces around =.
So it should be:
temp=${tab[$i+1]}
tab[$i+1]=${tab[$i]}
tab[$i]=$temp
I understand that you are working on a class assignment or such and are restricted in how you are allowed to solve the problem. For those not so restricted, here is a simple solution using standard unix tools:
#!/bin/bash
( IFS=$'\n'; echo "$*" ) | sort -n
Sample usage:
$ script.sh 3 5 6 2 1
1
2
3
5
6
Explanation:
( IFS=$'\n'; echo "$*" )
This causes the command line arguments to be printed, one per line. This is in a subshell so that the assignment to IFS does not affect the rest of the script.
sort -n
-n tells sort to apply a numeric sort.
I can get this to work in ksh but not in bash which is really driving me nuts.
Hopefully it is something obvious that I'm overlooking.
I need to run an external command where each line of the output will be stored at an array index.
This simplified example looks like it is setting the array in the loop correctly however after the loop has completed those array assignments are gone? It's as though the loop is treated completely as an external shell?
junk.txt
this is a
test to see
if this works ok
testa.sh
#!/bin/bash
declare -i i=0
declare -a array
echo "Simple Test:"
array[0]="hello"
echo "array[0] = ${array[0]}"
echo -e "\nLoop through junk.txt:"
cat junk.txt | while read line
do
array[i]="$line"
echo "array[$i] = ${array[i]}"
let i++
done
echo -e "\nResults:"
echo " array[0] = ${array[0]}"
echo " Total in array = ${#array[*]}"
echo "The whole array:"
echo ${array[#]}
Output
Simple Test:
array[0] = hello
Loop through junk.txt:
array[0] = this is a
array[1] = test to see
array[2] = if this works ok
Results:
array[0] = hello
Total in array = 1
The whole array:
hello
So while in the loop, we assign array[i] and the echo verifies it.
But after the loop I'm back at array[0] containing "hello" with no other elements.
Same results across bash 3, 4 and different platforms.
Because your while loop is in a pipeline, all variable assignments in the loop body are local to the subshell in which the loop is executed. (I believe ksh does not run the command in a subshell, which is why you have the problem in bash.) Do this instead:
while read line
do
array[i]="$line"
echo "array[$i] = ${array[i]}"
let i++
done < junk.txt
Rarely, if ever, do you want to use cat to pipe a single file to another command; use input redirection instead.
UPDATE: since you need to run from a command and not a file, another option (if available) is process substitution:
while read line; do
...
done < <( command args ... )
If process substitution is not available, you'll need to output to a temporary file and redirect input from that file.
If you are using bash 4.2 or later, you can execute these two commands before your loop, and the original pipe-into-the-loop will work, since the while loop is the last command in the pipeline.
set +m # Turn off job control; it's probably already off in a non-interactive script
shopt -s lastpipe
cat junk.txt | while read line; do ...; done
UPDATE 2: Here is a loop-less solution based on user1596414's comment
array[0]=hello
IFS=$'\n' array+=( $(command) )
The output of your command is split into words based solely on newlines (so that each line is a separate word), and appends the resulting line-per-slot array to the original. This is very nice if you are only using the loop to build the array. It can also probably be modified to accomodate a small amount of per-line processing, vaguely similar to a Python list comprehension.
I am having some trouble adding a string to an array within a loop. For some reason it always adds the same line. Here is my code:
declare -a properties
counter=0
while read line
do
if [[ ${line} == *=* ]]
then
properties[${counter}]=${line}
(( counter=counter + 1 ))
fi
done < ${FILE}
for x in ${!properties[#]}
do
echo "the value is $properties[x]"
done
For some reason each element in the array is the first line in the file. I must be doing something wrong, just not sure what.
Any help would be greatly appreciated
Try this script:
declare -a properties
while read line
do
[[ "${line}" == *=* ]] && properties+=("$line")
done < "${FILE}"
for x in "${properties[#]}"
do
echo "the value is "$x"
done
As #twalberg mentions in this comment, the problem is not in the top loop, but in the bottom one:
for x in ${!properties[#]}
do
echo "the value is $properties[x]"
done
Array references always need braces { ... } to expand properly.
For some reason each element in the array is the first line in the
file.
Not so. The array is correctly populated, but you need to change the reference to the array from:
echo "the value is $properties[x]"
to:
echo "the value is ${properties[x]}"
Just a simple oversight.
A much simpler way to add an element to an array is to simply use the syntax:
VARNAME+=("content")
Also, as written, your bug may be here:
(( counter=counter + 1 ))
It probably should be one of these three:
(( counter=$counter + 1 ))
counter+=1
counter=$[$counter+1]
counter=$(($counter + 1))
This KornShell (ksh) script worked fine for me. Let me know if anything.
readFileArrayExample.ksh
#! /usr/bin/ksh
file=input.txt
typeset -i counter=0
while read line
do
if [[ ${line} == *=* ]]; then
properties[${counter}]="${line}"
((counter = counter + 1))
echo "counter:${counter}"
fi
done < "${file}"
#echo ${properties[*]}
for x in "${properties[#]}"
do
echo "${x}"
done
readFileArrayExample.ksh Output:
#:/tmp #ksh readFileArrayExample.ksh
counter:1
counter:2
counter:3
a=b
a=1
b=1
#:/tmp #
input.txt
a-b
a+b
a=b
a=1
b=1
1-a
I can get this to work in ksh but not in bash which is really driving me nuts.
Hopefully it is something obvious that I'm overlooking.
I need to run an external command where each line of the output will be stored at an array index.
This simplified example looks like it is setting the array in the loop correctly however after the loop has completed those array assignments are gone? It's as though the loop is treated completely as an external shell?
junk.txt
this is a
test to see
if this works ok
testa.sh
#!/bin/bash
declare -i i=0
declare -a array
echo "Simple Test:"
array[0]="hello"
echo "array[0] = ${array[0]}"
echo -e "\nLoop through junk.txt:"
cat junk.txt | while read line
do
array[i]="$line"
echo "array[$i] = ${array[i]}"
let i++
done
echo -e "\nResults:"
echo " array[0] = ${array[0]}"
echo " Total in array = ${#array[*]}"
echo "The whole array:"
echo ${array[#]}
Output
Simple Test:
array[0] = hello
Loop through junk.txt:
array[0] = this is a
array[1] = test to see
array[2] = if this works ok
Results:
array[0] = hello
Total in array = 1
The whole array:
hello
So while in the loop, we assign array[i] and the echo verifies it.
But after the loop I'm back at array[0] containing "hello" with no other elements.
Same results across bash 3, 4 and different platforms.
Because your while loop is in a pipeline, all variable assignments in the loop body are local to the subshell in which the loop is executed. (I believe ksh does not run the command in a subshell, which is why you have the problem in bash.) Do this instead:
while read line
do
array[i]="$line"
echo "array[$i] = ${array[i]}"
let i++
done < junk.txt
Rarely, if ever, do you want to use cat to pipe a single file to another command; use input redirection instead.
UPDATE: since you need to run from a command and not a file, another option (if available) is process substitution:
while read line; do
...
done < <( command args ... )
If process substitution is not available, you'll need to output to a temporary file and redirect input from that file.
If you are using bash 4.2 or later, you can execute these two commands before your loop, and the original pipe-into-the-loop will work, since the while loop is the last command in the pipeline.
set +m # Turn off job control; it's probably already off in a non-interactive script
shopt -s lastpipe
cat junk.txt | while read line; do ...; done
UPDATE 2: Here is a loop-less solution based on user1596414's comment
array[0]=hello
IFS=$'\n' array+=( $(command) )
The output of your command is split into words based solely on newlines (so that each line is a separate word), and appends the resulting line-per-slot array to the original. This is very nice if you are only using the loop to build the array. It can also probably be modified to accomodate a small amount of per-line processing, vaguely similar to a Python list comprehension.