bash script shuf and save in list - arrays

I have A numbers and I want to chose B of them without repeting and save into a list.
Like this:
A="100"
B=5
a=$(gshuf -i 1-$B -n $A)
for i in ${a}
do
echo $i
done
How Can I do it?
A is a string in my code and I am using gshuf instead of shuf because I am in a mac

You may use process substitution:
A="100"
B=5
while read -r i; do
echo "$i"
done < <(gshuf -i 1-$B -n $A)
If you want to save generated numbers in array then use:
arr=()
while read -r i; do
arr+=("$i")
done < <(gshuf -i 1-$B -n $A)
Check content:
declare -p arr

Related

Put every other value into a seperate array

I have a basic array at the moment and it works as expected. But what I want to do is put every other value into a seperate array. I just need the 100 and 200 to be in array 1, and 1000 and 2000 to be in array 2. How do I do that?
$ cat scriptx.sh
while read line
do
my_array=("${my_array[#]}" $line)
done
echo ${my_array[#]}
$ ./scriptx.sh
100
1000
200
2000
100 1000 200 2000
For an arbitrary number of arrays n_arrays, you can use a counter of lines read so far, dynamically generate array names, such as array_0, array_1 etc., and append new lines to the right arrays based on counter % n_arrays. Obviously, n_arrays set to 2 gives an answer to the question.
#!/bin/bash
set -euo pipefail
declare -ri n_arrays=2 # number of arrays to use
declare -i i counter=0 # number of lines read so far
for ((i = 0; i < n_arrays; ++i)); do
declare -a "array_${i}" # optional; needed with set -u
done
while IFS= read -r line; do
declare -n my_array="array_$((counter++ % n_arrays))"
my_array+=("$line") # append line to the right array
done
for ((i = 0; i < n_arrays; ++i)); do
declare -n my_array="array_${i}"
echo "${my_array[#]#A}" # print a declare+assign command
done
Something along the lines of:
#!/bin/bash
while IFS= read -r line; do
array1+=("$line")
if IFS= read -r line; then
array2+=("$line")
fi
done
echo "array1: ${array1[*]}"
echo "array2: ${array2[*]}"
You could do something like this:
#!/bin/bash
i=0
while read line; do
if (( i++ % 2 )); then
odd_indexes+=($line)
else
even_indexes+=($line)
fi
done
echo "even_indexes: ${even_indexes[*]}"
echo "odd_indexes: ${odd_indexes[*]}"
Assuming that your input data is:
100
1000
200
2000
This script will output:
even_indexes: 100 200
odd_indexes: 1000 2000
But you could also do something like this if you don't mind reading the input file twice:
#!/bin/bash
mapfile -t odd_indexes < <(sed -n 'n;p' data.txt)
mapfile -t even_indexes < <(sed -n 'p;n' data.txt)
echo "even_indexes: ${even_indexes[*]}"
echo "odd_indexes: ${odd_indexes[*]}"
If you're reading from stdin you would need to write that out to a temporary file and use that file in the above script.
One idea:
$ cat runme
#!/bin/bash
cnt=${1:-2} # 1st (optional) arg states number of arrays; if not supplied default is '2'
for ((i=1;i<=cnt;i++))
do
unset array_"$i" # make sure arrays do not exist
done
n=0
while read -r line
do
(( ndx = n++%cnt +1 )) # calculate array index
declare -n curr_array=array_"${ndx}" # use nameref to dynamically reference correct array
curr_array+=( "${line}" ) # add line to array
done
echo ""
for ((i=1;i<=cnt;i++)) # display contents of our arrays
do
typeset -p array_"$i"
done
Taking for a test drive:
$ runme
100
1000
200
2000
declare -a array_1=([0]="100" [1]="200")
declare -a array_2=([0]="1000" [1]="2000")
$ runme 2
100
1000
200
2000
declare -a array_1=([0]="100" [1]="200")
declare -a array_2=([0]="1000" [1]="2000")
$ runme 3
1 a
2 b
3 c
4 d
5 e
declare -a array_1=([0]="1 a" [1]="4 d")
declare -a array_2=([0]="2 b" [1]="5 e")
declare -a array_3=([0]="3 c")

Split two numbers in two arrays

I need to split 2 numbers in the form(they are from a text file):
Num1:Num2
Num3:Num4
And store num1 into array X and number 2 in array Y num 3 in array X and num4 in array Y.
With bash:
mapfile -t X < <(cut -d : -f 1 file) # read only first column
mapfile -t Y < <(cut -d : -f 2 file) # read only second column
declare -p X Y
Output:
declare -a X='([0]="num1" [1]="num3")'
declare -a Y='([0]="num2" [1]="num4")'
Disadvantage: The file is read twice.
You could perform the following steps:
Create destination arrays empty
Read file line by line, with a classic while read ... < file loop
Split each line on :, again using read
Append values to arrays
For example:
arr_x=()
arr_y=()
while IFS= read line || [ -n "$line" ]; do
IFS=: read x y <<< "$line"
arr_x+=("$x")
arr_y+=("$y")
done < data.txt
echo "content of arr_x:"
for v in "${arr_x[#]}"; do
echo "$v"
done
echo "content of arr_y:"
for v in "${arr_y[#]}"; do
echo "$v"
done
Here is a quick bash solution:
c=0
while IFS=: read a b ;do
x[$c]="$a"
y[$c]="$b"
c=$((c+1))
done < input.txt
We send the input.txt to a while loop, using Input Field Separator : and read the first number of each line as $a and second number as $b. Then we add them to the array as you specified. We use a counter $c to iterate the location in the arrays.
Using =~ operator to store the pair of numbers to array $BASH_REMATCH:
$ cat file
123:456
789:012
$ while read -r line
do
[[ $line =~ ([^:]*):(.*) ]] && echo ${BASH_REMATCH[1]} ${BASH_REMATCH[2]}
# do something else with numbers as they will be replaced on the next iteration
done < file

Fetching data into an array

I have a file like this below:
-bash-4.2$ cat a1.txt
0 10.95.187.87 5444 up 0.333333 primary 0 false 0
1 10.95.187.88 5444 up 0.333333 standby 1 true 0
2 10.95.187.89 5444 up 0.333333 standby 0 false 0
I want to fetch the data from the above file into a 2D array.
Can you please help me with a suitable way to put into an array.
Also post putting we need put a condition to check whether the value in the 4th column is UP or DOWN. If it's UP then OK, if its down then below command needs to be executed.
-bash-4.2$ pcp_attach_node -w -U pcpuser -h localhost -p 9898 0
(The value at the end is getting fetched from the 1st column.
You could try something like that:
while read -r line; do
declare -a array=( $line ) # use IFS
echo "${array[0]}"
echo "${array[1]}" # and so on
if [[ "$array[3]" ]]; then
echo execute command...
fi
done < a1.txt
Or:
while read -r -a array; do
if [[ "$array[3]" ]]; then
echo execute command...
fi
done < a1.txt
This works only if field are space separated (any kind of space).
You could probably mix that with regexp if you need more precise control of the format.
Firstly, I don't think you can have 2D arrays in bash. But you can however store lines into a 1-D array.
Here is a script ,parse1a.sh, to demonstrate emulation of 2D arrays for the type of data you included:
#!/bin/bash
function get_element () {
line=${ARRAY[$1]}
echo $line | awk "{print \$$(($2+1))}" #+1 since awk is one-based
}
function set_element () {
line=${ARRAY[$1]}
declare -a SUBARRAY=($line)
SUBARRAY[$(($2))]=$3
ARRAY[$1]="${SUBARRAY[#]}"
}
ARRAY=()
while IFS='' read -r line || [[ -n "$line" ]]; do
#echo $line
ARRAY+=("$line")
done < "$1"
echo "Full array contents printout:"
printf "%s\n" "${ARRAY[#]}" # Full array contents printout.
for line in "${ARRAY[#]}"; do
#echo $line
if [ "$(echo $line | awk '{print $4}')" == "down" ]; then
echo "Replace this with what to do for down"
else
echo "...and any action for up - if required"
fi
done
echo "Element access of [2,3]:"
echo "get_element 2 3 : "
get_element 2 3
echo "set_element 2 3 left: "
set_element 2 3 left
echo "get_element 2 3 : "
get_element 2 3
echo "Full array contents printout:"
printf "%s\n" "${ARRAY[#]}" # Full array contents printout.
It can be executed by:
./parsea1 a1.txt
Hope this is close to what you are looking for. Note that this code will loose all indenting spaces during manipulation, but a formatted update of the lines could solve that.

Nested array inside variable in for loop

I'm beginning in Bash and I try to do this, but I can't.
Example:
array=("/dev/sda1" "/dev/sdb1")
for i in "${array[#]";do
space=$(df -H | grep ${array[1]})
done
or this:
i=0
for i in ("/dev/sda1" "/dev/sdb1")
space=$(df -h | grep (($i++)))
done
Is this possible?
You can directly feed the array into df like below and avoid using for-loop like
df -H "${array[#]}"
However, if this is a venture to study the for-loop in bash then you can do the same like below
for i in "${array[#]}"
do
df -H "$i"
# Well, "${array[#]}" expands to everything in array
# and i takes each value in each turn.
done
And if you wish to access the array using index, then
for ((i = 0; i < ${#array[#]}; i++)) # This is a c-style for loop
do
df -H "${array[i]}" # Note the index i is not prefixed with $
done
Edit
To check if the usage is more than, say, 10GB
# We will usage file which we would use to fill the body of mail
# This file will contain the file system usage
# But first make sure the file is empty before every run
cat /dev/null > /tmp/diskusagereport
for i in "${array[#]}"
do
usage=$(df -H "$i" | awk 'END{sub(/G$/,"",$3);print $3}')
if [ "${usage:-0}" -gt 10 ]
then
echo "Filesystem : ${i} usage : ${usage}GB is greater than 10GB" >> /tmp/diskusagereport
fi
done
#finally use the `mailx` client like below
mailx -v -r "from#address.in" -s "Disk usage report" \
-S smtp="smtp.yourserver.org:port" -S smtp-auth=login \
-S smtp-auth-user="your_auth_user_here" \
-S smtp-auth-password='your_auth_password' \
to#address.com </tmp/diskusagereport

How to deny empty array indexes in bash

I wrote the bash like so:
#!/bin/bash
GAP=1
Out=$1
ResultFile=$2
len=`wc -l $Out | awk '{print $1}'`
eval "(COMMAND) &"
pid=$!
i=0
while kill -0 $pid; do
if [ -N $Out ]; then
newlen=`wc -l $Out | awk '{print $1}'`
newlines=`expr $newlen - $len`
tail -$newlines $Out > temp
IP=( $(sed -n '<SomeThing>' temp) )
host=${IP[$i]}
echo "exit" | nc $host 23
if [ "$?" -eq "0" ]; then
(
<DoingSomeThing>
) | nc $host 23 1>>$ResultFile 2>&1
fi
len=$newlen
let i++
fi
sleep $GAP
done
When the command IP=( $(sed -n '<SomeThing>' temp) ) is running in my bash maybe the result of sed command is nothing and maybe the output is ip. I want only when output of sed command get ip write it into array and when the output of sed is empty does not write it to array.
Thank you
You're not doing your script right in many ways but about your question, the quick way is to store the output first on a variable:
SED_OUT=$(sed -n '<SomeThing>' temp)
[[ -n $SED_OUT ]] && IP=($SED_OUT) ## Would only alter IP if $SED_OUT has a value.

Resources