For a given script I can supply one argument that has the following form:
-u[number][letter][...]
Examples: -u2T34T120F -u1T2T10F
Letters are either T or F, and the number is an integer number, which can be up to 999.
I would like a write loop where in each iteration the number is stored in variable "a" and the corresponding letter in variable "b". The loop goes through all the number-letter pairs in the argument.
For the first example, the argument is -u2T34T120F the iterations would be:
First: a=2 b=T
Second: a=34 b=T
Third: a=120 b=F
End of loop
Any suggestion is most welcome.
Here's one way to do it with GNU awk:
<<<"2T34T120F" \
awk -v RS='[TF]' 'NF { printf "a: %3d b: %s\n", $0, RT }'
Output:
a: 2 b: T
a: 34 b: T
a: 120 b: F
To use this in a bash while-loop do something like this:
<<<"2T34T120F" \
awk 'NF { print $0, RT }' RS='[TF]' |
while read a b; do
echo Do something with $a and $b
done
Output:
Do something with 2 and T
Do something with 34 and T
Do something with 120 and F
$ var='-u2T34T120F'
$ a=($(grep -o '[0-9]*' <<< "$var"))
$ b=($(grep -o '[TF]' <<< "$var"))
$ echo ${a[0]} ${a[1]} ${a[2]}
2 34 120
$ echo ${b[0]} ${b[1]} ${b[2]}
T T F
how about this:
kent$ while IFS=' ' read a b; do echo "we have a:$a,b:$b\n---"; done<<< $(echo '-u2T34T120F'|sed 's/^-u//;s/[TF]/ &\n/g')
we have a:2,b:T
---
we have a:34,b:T
---
we have a:120,b:F
---
clear version:
while IFS=' ' read a b
do
echo "we have a:$a,b:$b\n---";
done<<< $(echo '-u2T34T120F'|sed 's/^-u//;s/[TF]/ &\n/g')
You can use parameter expansion in bash:
#! /bin/bash
set -- -u2T34T120F # Set the $1.
string=${1#-u} # Remove "-u".
while [[ $string ]] ; do
a=${string%%[FT]*} # Everything before the first F or T.
string=${string#$a} # Remove the $a from the beginning of the string.
b=${string%%[0-9]*} # Everything before the first number.
string=${string#$b} # Remove the $b from the beginning of the string.
echo $a $b
done
Or, using the same technique, but with arrays:
a=(${string//[TF]/ }) # Remove letters.
b=(${string//[0-9]/ }) # Remove numbers.
for (( i=0; i<${#a[#]}; i++ )) ; do
echo ${a[i]} ${b[i]}
done
Related
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
in bash,now i have two strings,one is 'a b c' while another is '1 2 3'
any good way to combine them to 'a=1 b=2 c=3'
I tried string to array and combined them.but if i don't know the IFS?
IFS=' ' read -r -a array1 <<< "$upvote_count"
IFS=' ' read -r -a array0 <<< "$qids"
tLen=${#array[#]}
for (( i=0; i<${tLen}; i++ ));
do
echo "${array0[$i]}"" ""${array1[$i]}">>a.txt
done
You can create arrays from each string[1] and then use eval to create the variables with names from string 1 and values from string 2 by looping over each array element and evaluating array1[i]=array2[i] (pseudocode). A short script would look like the following:
#!/bin/bash
v1="a b c" ## original string variables
v2="1 2 3"
ar1=( $(echo $v1) ) ## create arrays 1 & 2 from strings
ar2=( $(echo $v2) )
for ((i=0; i<${#ar1[#]}; i++)); do
eval "${ar1[i]}=${ar2[i]}" ## eval to create variables
done ## (be careful with eval)
printf "a=%s\nb=%s\nc=%s\n" $a $b $c ## confirm
Output
$ bash evalabc.sh
a=1
b=2
c=3
You would want to add validations that the you have the same number of elements in each array, that the elements of the first don't begin with numbers, etc.. and that they do not contain anything that would be harmful when you run eval!
As noted in the comment of the script, take great care in using eval (or avoid it altogether) because it will do exactly what you tell it to do. You would not want to have, e.g. a=sudo rm, b="-rf", c=/* and then eval "$a $b $c" -- very bad things can happen.
Footnotes:
[1] (adjust IFS as needed - not needed for space separation)
Give this tested version a try:
unset letters; unset figures; IFS=' ' read -r -a letters <<< "a b c" ; \
IFS=' ' read -r -a figures <<< '1 2 3' ; \
for i in "${!letters[#]}" ; do \
printf "%s=%s\n" ${letters[i]} ${figures[i]}; \
done
a=1
b=2
c=3
A general method that works in any Bourne shell, (not just bash): Use a for loop for one list (a b c), match it up with piped input for the second list (1 2 3), produce a string of shell code, and eval that.
eval $(seq 3 | for f in a b c ; do read x ; echo $f=$x ; done) ;
echo $a $b $c
which prints:
1 2 3
eval is needed because variables assigned in a loop are forgotten once the loop is over.
Caution: never let eval execute unknown code. If either list contained unwanted shell code delimiters or commands, further parsing would be necessary, i.e. a prophylactic pipe after '; done'.
Applied to the specific example in the starting question, with two strings, containing space separated lists, we get:
qids="a b c" # our variable names
unset $qids # clear them if need be
upvote_count="1 2 3" # the values to be assigned
# generate code to assign the names list to the values list,
# via a 'for' loop, the index $var_name is for the $qids,
# and assign $var_name to each $upvote_count value which we pipe in.
# Since an assignment can't leave the loop, we 'echo'
# the code for assignment, and 'eval' that code after the loop
# is done.
eval $(
echo "${upvote_count}" |
tr ' ' '\n' |
for var_name in $qids ; do
read value
echo "$var_name=$value"
done
)
# The loop is done, so test if the code worked:
for var_name in $qids
do
echo -n $var_name=
eval echo \$$var_name
done
...which outputs:
a=1
b=2
c=3
How can I increment characters similar to how numbers are done in bash?
Example; aaa -> zzz
for i in {aaa..zzz}; do
echo -n $i;
done
Should result in:
aaa aab aac (...) zzx zzy zzz
printf '%s ' {a..z}{a..z}{a..z}
If you really want to increment a character, you have to jump through some hoops:
First, you have to get the ordinal value of the character. This can be done with the shell's weird leading-quote syntax:
$ printf -v ordA '%d' '"A'
$ echo "$ordA"
65
Next, you need to add one to that:
$ ordB=$(( 1 + ordA ))
$ echo "$ordB"
66
Then you need to format that value so it can be printfed as a character:
$ printf -v fmtB '\\x%x' "$ordB"
$ echo "$fmtB"
\x42
Then, you finally printf it:
$ printf -v chrB "$fmtB"
$ echo "$chrB"
B
Whew. I'm sure some of that can be simplified, but those're the actual steps that need to be taken.
echo {a..z}{a..z}{a..z}
would suffice.
for n in {a..z}{a..z}{a..z}; do echo -n " $n"; done
Expanding on a comment to the answer by kojiro, if you really want to know how to increment an alphabetic string (as opposed to enumerating all possibilities), here's a solution (bash only, and it depends on the shell option extglob). It only works on strictly alphabetic lower-case strings, but it should be "obvious" how to extend it:
inc () {
local pfx=${1%%[^z]*(z)};
[[ $pfx != $1 ]] && echo $pfx$(tr a-z b-za <<<${1:${#pfx}})
}
Example:
$ a=zyy; while echo $a; a=$(inc $a); do :; done
zyy
zyz
zza
zzb
zzc
zzd
zze
zzf
zzg
zzh
zzi
zzj
zzk
zzl
zzm
zzn
zzo
zzp
zzq
zzr
zzs
zzt
zzu
zzv
zzw
zzx
zzy
zzz
I'm trying to list script's argument by printf function. Arguments are counted by iteration of $i. What should be in printf function?
I need something like
eval echo \$$i
but in printf function.
Edit: Have while cycle with iteration of $i and among other code, I have
printf "%s" $i
But, instead of $i, I need some code, that shows me value of argument.
In my case, it is name of file, and I need list them. One file in one iteration.
As noted in a comment, you normally do that with a loop such as:
i=1
for arg in "$#"
do
echo "$i: [$arg]"
((i++))
done
(If echo isn't allowed, use printf "%s\n" … where the … is whatever would have followed echo.)
You might also use indirect expansion to avoid the use of eval:
for i in 1 2 3 4; do echo "$i: [${!i}]"; done
You can generalize that with:
for i in $(seq 1 $#); do echo "$i: [${!i}]"; done
or
for ((i = 1; i <= $#; i++)); do echo "$i: [${!i}]"; done
For example, given:
set -- a b 'c d' ' e f '
all the loops produce the output:
1: [a]
2: [b]
3: [c d]
4: [ e f ]
The square brackets are merely there to delimit the argument values; it allows you to see the trailing blanks on the fourth line of output.
You might also be able to use:
printf "[%s]\n" "$#"
to get:
[a]
[b]
[c d]
[ e f ]
It is not very clear what you are asking for, but this will list the arguments passed to the script:
while [ $# -gt 0 ]; do
printf "%s\n" "$1"
shift
done
I guess what you are asking for is how to make the indirect reference to positional parameter i (i containing the position):
print "%s\n" ${!i}
To get your arguments in such a way that they can be fed back into the shell with the exact same value, the following bash extension can be used:
printf '%q ' "$#"; printf '\n'
This works even for rather unusual cases. Let's say that one of your arguments contains a newline:
./your-script 'hello
world' 'goodbye world'
This will be represented in the printf output:
$'hello\nworld' goodbye\ world
...with something you can use again in the shell:
$ echo $'hello\nworld' goodbye\ world
hello
world goodbye world
I want make a bash script which returns the position of an element from an array by give an arg. See code below, I use:
#!/bin/bash
args=("$#")
echo ${args[0]}
test_array=('AA' 'BB' 'CC' 'DD' 'EE')
echo $test_array
elem_array=${#test_array[#]}
for args in $test_array
do
echo
done
Finally I should have output like:
$script.sh DD
4
#!/bin/bash
A=(AA BB CC DD EE)
for i in "${!A[#]}"; do
if [[ "${A[i]}" = "$1" ]]; then
echo "$i"
fi
done
Note the "${!A[#]}" notation that gives the list of valid indexes in the array. In general you cannot just go from 0 to "${#A[#]}" - 1, because the indexes are not necessarily contiguous. There can be gaps in the index range if there were gaps in the array element assignments or if some elements have been unset.
The script above will output all indexes of the array for which its content is equal to the first command line argument of the script.
EDIT:
In your question, you seem to want the result as a one-based array index. In that case you can just increment the result by one:
#!/bin/bash
A=(AA BB CC DD EE)
for i in "${!A[#]}"; do
if [[ "${A[i]}" = "$1" ]]; then
let i++;
echo "$i"
fi
done
Keep in mind, though, that this index will have to be decremented before being used with a zero-based array.
Trying to avoid complex tools:
test_array=('AA' 'BB' 'CC' 'D D' 'EE')
OLD_IFS="$IFS"
IFS="
"
element=$(grep -n '^D D$' <<< "${test_array[*]}" | cut -d ":" -f 1)
IFS="$OLD_IFS"
echo $element
However, it consumes 2 processes. If we allow ourselves sed, we could do it with a single process:
test_array=('AA' 'BB' 'CC' 'D D' 'EE')
OLD_IFS="$IFS"
IFS="
"
element=$(sed -n -e '/^D D$/=' <<< "${test_array[*]}")
IFS="$OLD_IFS"
echo $element
Update:
As pointed out by thkala in the comments, this solution is broken in 3 cases. Be careful not to use it if:
You want zero indexed offset.
You have newlines in your array elements.
And you have a sparse array, or have other keys than integers.
Loop over the array and keep track of the position.
When you find the element matching the input argument, print out the position of the element. You need to add one to the position, because arrays have zero-based indexing.
#! /bin/sh
arg=$1
echo $arg
test_array=('AA' 'BB' 'CC' 'DD' 'EE')
element_count=${#test_array[#]}
index=0
while [ $index -lt $element_count ]
do
if [ "${test_array[index]}" = "$arg" ]
then
echo $((index+1))
break
fi
((index++))
done
Without loop:
#!/bin/bash
index() {
local IFS=$'\n';
echo "${*:2}" | awk '$0 == "'"${1//\"/\\\"}"'" { print NR-1; exit; }'
}
array=("D A D" "A D" bBb "D WW" D "\" D \"" e1e " D " E1E D AA "" BB)
element=${array[5]}
index "$element" "${array[#]}"
Output:
5