bash read strings and output as one key and multiple values - bash

Assuming there is an input:
1,2,C
We are trying to output it as
KEY=1, VAL1=2, VAL2=C
So far trying to modify from here:
Is there a way to create key-value pairs in Bash script?
for i in 1,2,C ; do KEY=${i%,*,*}; VAL1=${i#*,}; VAL2=${i#*,*,}; echo $KEY" XX "$VAL1 XX "$VAL2"; done
Output:
1 XX 2,c XX c
Not entirely sure what the pound ("#") and % here mean above, making the modification kinda hard.
Could any guru enlighten? Thanks.

I would generally prefer easier to read code, as bash can get ugly pretty fast.
Try this:
key_values.sh
#!/bin/bash
IFS=,
count=0
# $* is the expansion of all the params passed in, i.e. $1, $2, $3, ...
for i in $*; do
# '-eq' is checking for equality, i.e. is $count equal to zero.
if [ $count -eq 0 ]; then
echo -n "KEY=$i"
else
echo -n ", VAL${count}=$i"
fi
count=$(( $count + 1 ))
done
echo
Example
key_values.sh 1,2,ABC,123,DEF
Output
KEY=1, VAL1=2, VAL2=ABC, VAL3=123, VAL4=DEF

Expanding on anishsane's comment:
$ echo $1
1,2,3,4,5
$ IFS=, read -ra args <<<"$1" # read into an array
$ out="KEY=${args[0]}"
$ for ((i=1; i < ${#args[#]}; i++)); do out+=", VAL$i=${args[i]}"; done
$ echo "$out"
KEY=1, VAL1=2, VAL2=3, VAL3=4, VAL4=5

Related

Bash to split string and numbering it just like Python enumerate function

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

Sum of integer variables bash script

How can I do the sum of integers with bash script I read some variables with a for and I need to do the sum.
I have written the code like this:
Read N
Sum=0
for ((i=1;i<=N;i++))
do
read number
sum=sum+number
done
echo $sum
Use the arithmetic command ((...)):
#! /bin/bash
read n
sum=0
for ((i=1; i<=n; i++)) ; do
read number
((sum+=number))
done
echo $sum
#!/bin/bash
echo "Enter number:"
read N
re='^[0-9]+$'
if ! [[ ${N} =~ ${re} ]]
then
echo "Error. It's not a number"
exit 1
fi
Sum=0
for ((i=1;i<=N;i++))
do
sum=$((${sum} + ${i}))
done
echo "${sum}"
Well, not a straight bash solution, but you can also use seq and datamash (https://www.gnu.org/software/datamash/):
#!/bin/bash
read N
seq 1 $N | datamash sum 1
It is really simple (and it has its limitations), but it works. You can use other options on seq for increments different than 1 and so on.
It is also possible to declare a variable as an integer with declare -i. Any assignment to that variable is then evaluated as an arithmetic expression:
#!/bin/bash
declare -i sum=0
read -p "Enter n: " n
for ((i=1; i<=n; i++)) ; do
read -p "Enter number #$i: " number
sum+=number #sum=sum+number would also work
done
echo "Sum: $sum"
See Bash Reference Manual for more information. Using arithmetic command ((...)) is preferred, see choroba's answer.
$ declare -i var1=1
$ var2=1
$ var1+=5
$ echo "$var1"
6
$ var2+=5
$ echo "$var2"
15
This can be a tad confusing as += behaves differently depending on the variable's attributes. It's therefore better to explicitly use ((...)) for arithmetic operations.

bash awk split string into array

I am using awk to split a string into array using a specific delimiter. Now, I want to perform some operation on each element of the array.
I am able to extract a single element like this:
#! /bin/bash
b=12:34:56
a=`echo $b | awk '{split($0,numbers,":"); print numbers[1]}'`
echo $a
I want to do something like this:
#! /bin/bash
b=12:34:56
`echo $b | awk '{split($0,numbers,":");}'`
for(i=0;i<length(numbers);i++)
{
// perform some operation using numbers[i]
}
how would I do something like this in bash scripting?
None of these answers used awk (weird). With awk you can do something like:
echo 12:34:56 | awk '{split($0,numbers,":")} END {for(n in numbers){ print numbers[n] }}'
replacing print numbers[n] with whatever it is you want to do.
You don't really need awk for that, bash can do some string processing all by itself.
Try:
b=12:34:56
for element in ${b//:/ } ; do
echo $element
done
If you need a counter, it's pretty trivial to add that.
See How do I do string manipulations in bash? for more info on what you can do directly in bash.
b=12:34:56
IFS=:
set -- $b
for i; do echo $i; done
This does not contain bashisms but works with every sh.
The bash read command can split a string into an array by itself:
IFS=: read -a numbers <<< "$b"
To see that it worked:
echo "Hours: ${numbers[0]}"
echo "Minutes: ${numbers[1]}"
echo "Seconds: ${numbers[2]}"
for val in "${numbers[#]}"; do
seconds=$(( seconds * 60 + $val ))
done
Another neat way, not using awk, but build-in 'declare':
b=12:34:56
# USE IFS for splitting (and elements can have spaces in them)
IFS=":"
declare -a elements=( $b )
#show contents
for (( i=0 ; i < ${#elements[#]}; i++ )); do
echo "$i= ${elements[$i]}"
done

parse and expand interval

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[#]}

Loading variables from a text file into bash script

Is it possible to load new lines from a text file to variables in bash?
Text file looks like?
EXAMPLEfoo
EXAMPLEbar
EXAMPLE1
EXAMPLE2
EXAMPLE3
EXAMPLE4
Variables become
$1 = EXAMPLEfoo
$2 = EXAMPLEbar
ans so on?
$ s=$(<file)
$ set -- $s
$ echo $1
EXAMPLEfoo
$ echo $2
EXAMPLEbar
$ echo $#
EXAMPLEfoo EXAMPLEbar EXAMPLE1 EXAMPLE2 EXAMPLE3 EXAMPLE4
I would improve the above by getting rid of temporary variable s:
$ set -- $(<file)
And if you have as input a file like this
variable1 = value
variable2 = value
You can use following construct to get named variables.
input=`cat filename|grep -v "^#"|grep "\c"`
set -- $input
while [ $1 ]
do
eval $1=$3
shift 3
done
cat somefile.txt| xargs bash_command.sh
bash_command.sh will receive these lines as arguments
saveIFS="$IFS"
IFS=$'\n'
array=($(<file))
IFS="$saveIFS"
echo ${array[0]} # output: EXAMPLEfoo
echo ${array[1]} # output: EXAMPLEbar
for i in "${array[#]}"; do echo "$i"; done # iterate over the array
Edit:
The loop in your pastebin has a few problems. Here it is as you've posted it:
for i in "${array[#]}"; do echo " "AD"$count = "$i""; $((count=count+1)); done
Here it is as it should be:
for i in "${array[#]}"; do declare AD$count="$i"; ((count=count+1)); done
or
for i in "${array[#]}"; do declare AD$count="$i"; ((count++)); done
But why not use the array directly? You could call it AD instead of array and instead of accessing a variable called "AD4" you'd access an array element "${AD[4]}".
echo "${AD[4]}"
if [[ ${AD[9]} == "EXAMPLE value" ]]; then do_something; fi
This can be done be with an array if you don't require these variables as inputs to a script. push() function lifted from the Advanced Scripting Guide
push() # Push item on stack.
{
if [ -z "$1" ] # Nothing to push?
then
return
fi
let "SP += 1" # Bump stack pointer.
stack[$SP]=$1
return
}
The contents of /tmp/test
[root#x~]# cat /tmp/test
EXAMPLEfoo
EXAMPLEbar
EXAMPLE1
EXAMPLE2
EXAMPLE3
EXAMPLE4
SP=0; for i in `cat /tmp/test`; do push $i ; done
Then
[root#x~]# echo ${stack[3]}
EXAMPLE1
None of the above will work, if your values are quoted with spaces.
However, not everythinf is lost.
Try this:
eval "$(VBoxManage showvminfo "$VMname" --details --machinereadable | egrep "^(name|UUID|CfgFile|VMState)")"
echo "$name {$UUID} $VMState ($VMStateChangeTime) CfgFile=$CfgFile"
P.S.
Nothing will ever work, if your names are quoted or contain dashes.
If you have something like that, as is the case with VBoxManage output ("IDE-1-0"="emptydrive" and so on), either egrep only specific values, as shown in my example, or silence the errors.
However, silencing erors is always dangerous. You never know, when the next value will have unquoted "*" in it, thus you must treat values loaded this way very careful, with all due precaution.

Resources