I found interesting way to split string using tr or IFS
https://linuxhandbook.com/bash-split-string/
#!/bin/bash
#
# Script to split a string based on the delimiter
my_string="One;Two;Three"
my_array=($(echo $my_string | tr ";" "\n"))
#Print the split string
for i in "${my_array[#]}"
do
echo $i
done
Output
One
Two
Three
Based on this code, would be be possible to put a number in front of the string by using Bash?
In Python, there is enumerate function to accomplish this.
number = ['One', 'Two', 'Three']
for i,j in enumerate(number, 1):
print(f'{i} - {j}')
Output
1 - One
2 - Two
3 - Three
I belive there should be similar tricks can be done in Bash Shell probably with awk or sed, but I just can't think the solution for now.
I think you can just add something like count=$(($count+1))
#!/bin/bash
#
# Script to split a string based on the delimiter
my_string="One;Two;Three"
my_array=($(echo $my_string | tr ";" "\n"))
#Print the split string
count=0
for i in "${my_array[#]}"
do
count=$(($count+1))
echo $count - $i
done
This is a slightly modified version of #anubhava's answer.
y_string="One;Two;Three"
IFS=';' read -ra my_array <<< "$my_string"
# ${!array_name[#]} returns the indices/keys of the array
for i in "${!my_array[#]}"
do
echo "$((i+1)) - ${my_array[i]}"
done
From the bash manual,
It is possible to obtain the keys (indices) of an array as well as the values. ${!name[#]} and ${!name[*]} expand to the indices assigned in array variable name.
I saw you posted a post earlier today, sorry I failed to upload the code but still hope this could help you
my_string="AA-BBB"
IFS='-' read -ra my_array <<< "$my_string"
len=${#my_array[#]}
for (( i=0; i<$len; i++ )); do
up=$(($i % 2))
#echo $up
if [ $up -eq 0 ]
then
echo ${my_array[i]} = '"Country name"'
elif [ $up -eq 1 ]
then
echo ${my_array[i]} = '"City name"'
fi
done
Here is a standard bash way of doing this:
my_string="One;Two;Three"
IFS=';' read -ra my_array <<< "$my_string"
# builds my_array='([0]="One" [1]="Two" [2]="Three")'
# loop through array and print index+1 with element
# ${#my_array[#]} is length of the array
for ((i=0; i<${#my_array[#]}; i++)); do
printf '%d: %s\n' $((i+1)) "${my_array[i]}"
done
1: One
2: Two
3: Three
I am writing a shell script that contains a multi-threaded while loop. My loop iterates through the values of an array. Within the loop, I am calling a function. At the end of the function I am saving the results as a string variable. I want to add this string variable to an array on each iteration, and then be able to retrieve the contents of this array when the while loop completes.
From my understanding running the multi-threaded while loop, is what is causing for the array to be empty once the while loop completes. Each thread is ran in its own environment and the array value does not extend outside that environment. I would like to be able to extend this array value outside of the thread if possible. Currently I am just writing the string value to a temp file and then after the while loop, reading the contents of the temp file and saving that as my array. This method works, as the file generally isn't "too" large, but I would like to avoid writing to file if possible
My Code - doDeepLookup actually is a API call, but for the sake of argument lets just say it appends some text in-front of the read line from the while loop
#!/bin/bash
n=0
maxjobs=20
resultsArray=""
while IFS= read -r line
do
IPaddress="$(echo $line | sed 's/ /\n/g' | grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}")"
doDeepLookup "$line" "$IPaddress" &
if(( $(($((++n)) % $maxjobs)) == 0 )) ; then
wait
fi
done <<< "$(printf '%s\n' "${SomeOtherArray[#]}")"
printf '%s\n' "${resultsArray[#]}" #Returns NULL
doDeepLookup() {
results="$(echo "help me : $line")"
resultsArray+=($results)
}
Thanks to William
#!/bin/bash
n=0
maxjobs=20
WhileLoopFunction() {
resultsArray=""
while IFS= read -r line
do
IPaddress="$(echo $line | sed 's/ /\n/g' | grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}")"
doDeepLookup "$line" "$IPaddress" &
if(( $(($((++n)) % $maxjobs)) == 0 )) ; then
wait
fi
done <<< "$(printf '%s\n' "${SomeOtherArray[#]}")"
}
doDeepLookup() {
results="$(echo "help me : $line")"
echo $results
}
resultsArray=( $(WhileLoopFunction"${DeepArray[#]}") )
printf '%s\n' "${resultsArray[#]}"
With parset from GNU Parallel you would do something like:
parset resultsArray doDeepLookup ::: "${DeepArray[#]}"
printf '%s\n' "${resultsArray[#]}"
Why doesn't my for loop: for ((i=1 ;i<=$n, i++)) work? I can not figure this out. when n is for example 4, the line echo $n returns 4 but it does not go into the loop again. I don't get any errors either. I tried to make a small loop like:
for ((i=1, i<=$n; i++)); do
echo "this works"
done
This works fine which makes it even stranger to me :/. Thanks in advance
read n
length=16
p=()
p[1]=50
rest=63
function s() {
arr=($#)
line="_____________________________________________________________________"
for i in ${arr[#]}; do
line=$( echo $line | sed s/./1/$i)
done
echo $line
}
for ((i=1; i<=$n; i++)); do
echo $n
for ((j=1; j<=$length; j++)); do
s ${p[#]}
done
len=${#p[#]}
((len=$len*2))
for ((k=1; k<=$len; k+=2)); do
((p[$k+1]=p[$k]+1))
((p[$k]=p[$k]-1))
done
for ((l=1; l<=$length; l++)); do
s ${p[#]}
len=${#p[#]}
for ((m=1; m<=$len; m+=2)); do
((p[$m+1]=p[$m+1]+1))
((p[$m]=p[$m]-1))
done
done
((rest=$rest-2*$length))
((length=$length/2))
done
If you don't declare your variables local, they're global -- so the loop in your s function is overwriting the same i counter used outside the function, leading to the outer loop's early exit.
Consider using the below code:
s() {
local -a arr # declare a function-local array (sparse, like all bash arrays)
local i # ...and a function-local counter
for i; do # by default, a for statement iterates over "$#"; that works for us.
arr[$i]=1 # specifically set a value only for named items
done
# ...thereafter, iterate through the range of characters we want to print...
for ((i=0; i<70; i++)); do
printf '%s' "${arr[$i]:-_}" # and print the array entry if present, or an _ otherwise
done
printf '\n' # ...followed by a trailing newline.
}
How do I get an associative array from a query string in Bash? - Attempt:
#!/usr/bin/env bash
# Querystring (implementation stolen from http://stackoverflow.com/q/3919755)
function populate_querystring_array ()
{
param="$1"
query_dict="$2"
#for i in "${array[#]}"; do IFS="=" ; set -- $i; query_dict[$1]=$2; done
for ((i=0; i<${#param[#]}; i+=2))
do
query_dict[${param[i]}]=${param[i+1]}
done
}
q0='email=foo#bar.com&password=dfsa54'
declare -A querydict
populate_querystring_array "$q0" "$querydict"
printf "$querydict[email]"
#!/usr/bin/env bash
q0='email=foo#bar.com&password=dfsa54'
declare -A querydict
while IFS== read key value
do
querydict["$key"]="$value"
done < <(echo "$q0" | sed 's/&/\n/g' )
printf "${querydict[email]}\n"
In the above, 's/&/\n/g' is a sed command that replaces every occurrence of & with a new line. We apply this to q0 so that every parameter assignment is on a separate line. The parameter assignments are then read into the while loop. To read each assignment, IFS== read key value is used. IFS== tells read to treat the equal sign as a word separator. Thus, each assignment is broken into two words: the first is the key and the second is the value. These are then assigned to the associative array querydict with the statement querydict["$key"]="$value".
Putting it in a function
bash differs from most modern programming languages in that its facilities for passing complex data into and out of functions are extremely limited. In the method shown below, the associative array, querydict, is a global variable:
#!/usr/bin/env bash
declare -A querydict
populate_querystring_array () {
query="$1"
while IFS== read arg value
do
querydict["$arg"]="$value"
done < <(echo "$query" | sed 's/&/\n/g' )
}
q0='email=foo#bar.com&password=dfsa54'
populate_querystring_array "$q0"
printf "${querydict[email]}\n"
Below should work:
#!/bin/bash
function qrystring() {
qry=$1
while read key value; do
arr+=(["$key"]="$value")
done < <(awk -F'&' '{for(i=1;i<=NF;i++) {print $i}}' <<< $qry | awk -F'=' '{print $1" "$2}')
}
q='email=foo#bar.com&password=dfsa54'
declare -A arr
qrystring "$q"
for k in ${!arr[#]}; do
echo "$k -> ${arr[$k]}"
done
Explanation:
Im using a combination of awk commands to split the string into individual records first, then split on the = sign for kv pair.
I'm using process substitution here otherwise i would be populating a copy of the array.
EDIT:
Using a global variable to house array.
Taking #John1024's answer I use it in a function which returns the associative array by value, simply printing out the contents using array syntax:
function parseQuery {
local querystring="$*"
echo -n "("
echo "${querystring}" | sed 's/&/\n/g' | while IFS== read arg value
do
echo -n "[${arg}]='${value}' "
done
echo ")"
}
declare -A querydict=$(parseQuery "${QUERY_STRING}" )
In my script I need to expand an interval, e.g.:
input: 1,5-7
to get something like the following:
output: 1,5,6,7
I've found other solutions here, but they involve python and I can't use it in my script.
Solution with Just Bash 4 Builtins
You can use Bash range expansions. For example, assuming you've already parsed your input you can perform a series of successive operations to transform your range into a comma-separated series. For example:
value1=1
value2='5-7'
value2=${value2/-/..}
value2=`eval echo {$value2}`
echo "input: $value1,${value2// /,}"
All the usual caveats about the dangers of eval apply, and you'd definitely be better off solving this problem in Perl, Ruby, Python, or AWK. If you can't or won't, then you should at least consider including some pipeline tools like tr or sed in your conversions to avoid the need for eval.
Try something like this:
#!/bin/bash
for f in ${1//,/ }; do
if [[ $f =~ - ]]; then
a+=( $(seq ${f%-*} 1 ${f#*-}) )
else
a+=( $f )
fi
done
a=${a[*]}
a=${a// /,}
echo $a
Edit: As #Maxim_united mentioned in the comments, appending might be preferable to re-creating the array over and over again.
This should work with multiple ranges too.
#! /bin/bash
input="1,5-7,13-18,22"
result_str=""
for num in $(tr ',' ' ' <<< "$input"); do
if [[ "$num" == *-* ]]; then
res=$(seq -s ',' $(sed -n 's#\([0-9]\+\)-\([0-9]\+\).*#\1 \2#p' <<< "$num"))
else
res="$num"
fi
result_str="$result_str,$res"
done
echo ${result_str:1}
Will produce the following output:
1,5,6,7,13,14,15,16,17,18,22
expand_commas()
{
local arg
local st en i
set -- ${1//,/ }
for arg
do
case $arg in
[0-9]*-[0-9]*)
st=${arg%-*}
en=${arg#*-}
for ((i = st; i <= en; i++))
do
echo $i
done
;;
*)
echo $arg
;;
esac
done
}
Usage:
result=$(expand_commas arg)
eg:
result=$(expand_commas 1,5-7,9-12,3)
echo $result
You'll have to turn the separated words back into commas, of course.
It's a bit fragile with bad inputs but it's entirely in bash.
Here's my stab at it:
input=1,5-7,10,17-20
IFS=, read -a chunks <<< "$input"
output=()
for chunk in "${chunks[#]}"
do
IFS=- read -a args <<< "$chunk"
if (( ${#args[#]} == 1 )) # single number
then
output+=(${args[*]})
else # range
output+=($(seq "${args[#]}"))
fi
done
joined=$(sed -e 's/ /,/g' <<< "${output[*]}")
echo $joined
Basically split on commas, then interpret each piece. Then join back together with commas at the end.
A generic bash solution using the sequence expression `{x..y}'
#!/bin/bash
function doIt() {
local inp="${#/,/ }"
declare -a args=( $(echo ${inp/-/..}) )
local item
local sep
for item in "${args[#]}"
do
case ${item} in
*..*) eval "for i in {${item}} ; do echo -n \${sep}\${i}; sep=, ; done";;
*) echo -n ${sep}${item};;
esac
sep=,
done
}
doIt "1,5-7"
Should work with any input following the sample in the question. Also with multiple occurrences of x-y
Use only bash builtins
Using ideas from both #Ansgar Wiechers and #CodeGnome:
input="1,5-7,13-18,22"
for s in ${input//,/ }
do
if [[ $f =~ - ]]
then
a+=( $(eval echo {${s//-/..}}) )
else
a+=( $s )
fi
done
oldIFS=$IFS; IFS=$','; echo "${a[*]}"; IFS=$oldIFS
Works in Bash 3
Considering all the other answers, I came up with this solution, which does not use any sub-shells (but one call to eval for brace expansion) or separate processes:
# range list is assumed to be in $1 (e.g. 1-3,5,9-13)
# convert $1 to an array of ranges ("1-3" "5" "9-13")
IFS=,
local range=($1)
unset IFS
list=() # initialize result list
local r
for r in "${range[#]}"; do
if [[ $r == *-* ]]; then
# if the range is of the form "x-y",
# * convert to a brace expression "{x..y}",
# * using eval, this gets expanded to "x" "x+1" … "y" and
# * append this to the list array
eval list+=( {${r/-/..}} )
else
# otherwise, it is a simple number and can be appended to the array
list+=($r)
fi
done
# test output
echo ${list[#]}