I am looking to populate an array using the {a..z}. The end result is every letter from a-z stored in an array that can be used for referencing later.
code:
#!/bin/bash
#proof of concept
#echo {a..z}
#a b c d e f g h i j k l m n o p q r s t u v w x y z
#attempt 1
CHARSET=({a..z})
printf "${CHARSET[#]}"
#result: a
#attempt 2
CHARSET=({a..z})
for i in CHARSET ; do
echo "$1"
done
exit
#result a
Ultimately I am trying to test every permutation of a-z up to 4 characters long without making an intermediate file to read from e.g.
#!/bin/bash
for i in {a..z}; do
for j in {a..z}; do
for k in {a..z}; do
for l in {a..z}; do
echo $i >>test.txt #1 letter
echo $i$j >>test.txt #2 letters
echo $i$j$k >>test.txt #3 letters
echo $i$j$k$l >>test.txt #4 letters
done;done;done;done
test.txt
a
aa
aaa
aaaa
...........
z
zz
zzz
zzzz
I was hoping to be able to store a-z in an array then use that array each time to increase the letter count up to four. Or is there a much simpler way to succeed here? (Without creating the intermediate file as given in the example above)
You can append multiple brace expansions to combinatorially combine them:
for word in {a..z}{a..z}{a..z}{a..z}
do
echo "$word"
done
Related
I'm unable to find any for loop syntax that works with ksh for me. I'm wanting to create a program that esentially will add up numbers that are assigned to letters when a user inputs a word into the program. I want the for loop to read the first character, grab the number value of the letter, and then add the value to a variable that starts out equaled to 0. Then the next letter will get added to the variable's value and so on until there are no more letters and there will be a variable from the for loop that equals a value.
I understand I more than likely need an array that specifies what a letter's (a-z) value would be (1-26)...which I am finding difficult to figure that out as well. Or worst case I figure out the for loop and then make about 26 if statements saying something like if the letter equals c, add 3 to the variable.
So far I have this (which is pretty bare bones):
#!/bin/ksh
typeset -A my_array
my_array[a]=1
my_array[b]=2
my_array[c]=3
echo "Enter a word: \c"
read work
for (( i=0; i<${work}; i++ )); do
echo "${work:$i:1}"
done
Pretty sure this for loop is bash and not ksh. And the array returns an error of typeset: bad option(s) (I understand I haven't specified the array in the for loop).
I want the array letters (a, b, c, etc) to correspond to a value such as a = 1, b = 2, c = 3 and so on. For example the word is 'abc' and so it would add 1 + 2 + 3 and the final output will be 6.
You were missing the pound in ${#work}, which expands to 'length of ${work}'.
#!/bin/ksh
typeset -A my_array
my_array[a]=1
my_array[b]=2
my_array[c]=3
read 'work?enter a word: '
for (( i=0; i<${#work}; i++ )); do
c=${work:$i:1} w=${my_array[${work:$i:1}]}
echo "$c: $w"
done
ksh2020 supports read -p PROMPT syntax, which would make this script 100% compatible with bash. ksh93 does not. You could also use printf 'enter a word: ', which works in all three (ksh2020, ksh93, bash) and zsh.
Both ksh2020 and ksh93 understand read var?prompt which I used above.
First check the input work, you only want {a..z}.
charwork=$(tr -cd "[a-z]" <<< "$work")
Next you can fill 2 arrays with corresponding values
a=( {a..z} )
b=( {1..26} )
Using these arrays you can make a file with sed commands
for ((i=0;i<26;i++)); do echo "s/${a[i]}/${b[i]}+/g"; done > replaceletters.sed
# test it
echo "abcz" | sed -f replaceletters.sed
# result: 1+2+3+26+
Before you can pipe this into bc, use sed to remove the last + character.
Append s/+$/\n/ to replaceletters.sed and bc can calculate it.
Now you can use sed for replacing letters by digits and insert + signs.
Combining the steps, you have
a=( {a..z} )
b=( {1..26} )
tr -cd "[a-z]" <<< "$work" |
sed -f <(
for ((i=0;i<26;i++)); do
echo "s/${a[i]}/${b[i]}+/g"
done
echo 's/+$/\n/'
) | bc
In the loop you can use $i and avoid array b, but remember that the array start with 0, so a[5] corresponds with 6.
I'm creating a Caesar cipher that substitutes the letter in a word with the matching letter if the alphabet were reversed. The sample is "abcdefghijklmnopqrstuvwxyz". The output of the following code produces "abcdefghijklmmlkjihgfedcba". The desired output is the alphabet in reverse, but once the editor hits the midpoint, it goes back in reverse instead of going on through to the end.
declare -A origin
x=({a..z})
z=({z..a})
for i in {0..25}
do
origin[${x[i]}]=${z[i]}
done
for x in "${!origin[#]}"
do
sed -i 's/'${x}'/'${origin[${x}]}'/g' test.txt
done
Don't forget character indexes in bash. In your script, there is no need for the first 2 indexed arrays x & y. Example:
declare -A origin
x=abcdefghijklmnopqrstuvwxyz
z=zyxwvutsrqponmlkjihgfedcba
for i in {0..25}; do
origin[${x:i:1}]=${z:i:1}
done
Nothing like substituting origin[{a..z}] for {z..a} and getting a familiar looking result back? Look for example at the first and last iterations only. On the first iteration, you substitute all a's with z's. Then on the last iteration you again substitute all the z's (including those your previously replaced a->z in the first iteration) with a's again -- effectively undoing your changes.
A better example is to look at the midpoint of the alphabet m->n.
x=abcdefghijklmnopqrstuvwxyz
z=zyxwvutsrqponmlkjihgfedcba
||
When your iteration reaches m, you substitute all m's with n's. Then the very next iteration, you substitute n's with m's.
You can see how this happens to look like only half of the substitutions are being effected. After you reach the midpoint in origin, any substitutions only occur once since you are no longer encountering letters you have already substituted.
The solution using tr previously posted looks like one of your best options.
The aproach you used to pass the associative array is correct, but the logic of the Ceasar cipher is wrong.
Because for each iteration of the for loop the sed command changes some characters in the input file. This character can be the character originally in the input file, or a character which was previously changed by an earlier sed. So the for loop in effect would do multiple conversion than doing a single conversion.
For example
Consider an input file
$ cat test
a z
Now a small formated script would be
for x in "${origin[#]}"; do sed -i "s/$x/${origin[$x]}/g" test; echo "$x ${origin[$x]}"; done;
z a
y b
x c
w d
v e
..
..
Here in the first iteration, the sed would change the z to a. Now the input file would be
$cat test
a a
Now at the 25th iteration the $x will be a, Which will convert both the as in the input to z
$ cat test
z z
Alternate solution
An alternate solution can be written using tr as
$ a=$(echo {z..a} | tr -d " ")
$ b=$(echo {a..z} | tr -d " ")
$ echo {a..z} | tr $b $a
z y x w v u t s r q p o n m l k j i h g f e d c b a
Why does it work?
Here the characters are read from the input and changed with corresponding character in the tr argument, This ensures that a single character is changed only once.
I've found a solution to the problem. Thank you both for your help!
#!/bin/bash
#Retrieve the desired shift from user
echo "What number do you want to use for the shift?"
read num
#Create an array of all letters
x=({a..z})
#Take user input and use to create the cipher array
case "$num" in
0)
y=({a..z})
;;
1)
y=({{b..z},a})
;;
2)
y=({{c..z},a,b})
;;
3)
y=({{d..z},a,b,c})
;;
4)
y=({{e..z},a,b,c,d})
;;
5)
y=({{f..z},{a..e}})
;;
6)
y=({{g..z},{a..f}})
;;
7)
y=({{h..z},{a..g}})
;;
8)
y=({{i..z},{a..h}})
;;
9)
y=({{j..z},{a..i}})
;;
10)
y=({{k..z},{a..j}})
;;
11)
y=({{l..z},{a..k}})
;;
12)
y=({{m..z},{a..l}})
;;
13)
y=({{n..z},{a..m}})
;;
14)
y=({{o..z},{a..n}})
;;
15)
y=({{p..z},{a..o}})
;;
16)
y=({{q..z},{a..p}})
;;
17)
y=({{r..z},{a..q}})
;;
18)
y=({{s..z},{a..r}})
;;
19)
y=({{t..z},{a..s}})
;;
20)
y=({{u..z},{a..t}})
;;
21)
y=({{v..z},{a..u}})
;;
22)
y=({{w..z},{a..v}})
;;
23)
y=({{x..z},{a..w}})
;;
24)
y=({{y..z},{a..x}})
;;
25)
y=({{z..z},{a..y}})
;;
*)
echo "Sorry, you must use a shift from 0 to 25."
;;
esac
#create the string variables for manipulation
fromset=""
toset=""
#place the alphabetic arrays into the atring variables
for i in {0..25}
do
fromset="$fromset${x[i]}"
toset="$toset${y[i]}"
done
#Use sed text transformations to alter given files
sed "y/$fromset/$toset/" original.txt > encoded.txt
sed "y/$toset/$fromset/" encoded.txt > decoded.txt
I have a file with lines which I am taking input by $1:
X B C D E
X G H I J
X L M N
Y G
Z B
Y L
In each line starts with X, the key is the 2nd element and the values are the rest elements.
I am reading the file line by lines creating associate array for each.
while read LINE
do
INPUT=$(echo $LINE |awk '{print $1}')
if [[ "$INPUT" = X ]]
then
key_name=$(echo $LINE | awk '{print $2}')
declare -A dependencies
value_names=($(echo $LINE|awk '{$1=$2=""; print $0}'))
dependencies[key_name]=value_names
echo -e "\nvalues of $key_name are ${key_name[*]}\n"
sleep 1
fi
done < $1
So I am losing the value for each line reading.
But I need to store all the lines with X in the associate arays,
because I need to search for the key later for the later lines, lets say: a line start with Y, and it has G, so here I need to find the valuess from the associated arrays
with key G.
Can anyone suggest some idea how to store all lines start with X in a single associative array by reading line line the file? Or any better approach?
Here from the sample input given, the output will be in 3 lines:
H I J
C D E
M N
Here X,Y,X are recognizing the lines, what to do with the next characters. If X store the rest in KEY-PAIR or if Y or Z extract the values from associative arrays.
Using GNU awk for gensub():
$ gawk '{ if (/^X/) a[$2] = gensub(/(\S+\s+){2}/,"",""); else print a[$2] }' file
H I J
C D E
M N
The above implicitly loops through every line in the input file and when it finds a line that starts with X (/^X/) it removes the first 2 non-space-then-space pairs (gensub(/(\S+\s+){2}/,"","")) and stores the result in associative array a indexed by the original 2nd field (a[$2] = ...), so for example for input line X B C D E it saves a["B"] = "C D E". If the line did not start with X (else) then it prints the array indexed by the 2nd field in the current line, so for input line Z B it will execute print a["B"] and so output C D E.
With an old version of gawk (run gawk --version and check for version before 4.0) you might need:
$ gawk --re-interval '{ if (/^X/) a[$2] = gensub(/([^[:space:]]+[[:space:]]+){2}/,"",""); else print a[$2] }' file
but if so youre missing a lot of very useful functionality so get a new gawk!
The declaration should go outside the loop. The variable interpolations need a dollar sign in front. The rest is just refactoring.
declare -A dependencies
awk '$1=="X"{$1=""; print }' "$1" |
{ while read -r key value;
do
dependencies["$key"]="$value"
echo -e "\nvalues of $key_name are ${key_name[*]}\n"
#sleep 1
done
:
# do stuff with "${dependencies[#]}"
}
I'm trying to write a script in bash using an associative array.
I have a file called data:
a,b,c,d,e,f
g,h,i,j,k,l
The following script:
oldIFS=${IFS}
IFS=","
declare -A assoc
while read -a array
do
assoc["${array[0]}"]="${array[#]}"
done
for key in ${!assoc[#]}
do
echo "${key} ---> ${assoc[${key}]}"
done
IFS=${oldIFS}
gives me
a ---> a b c d e f
g ---> g h i j k l
I need my output to be:
a b ---> c d e f
g h ---> i j k l
oldIFS=${IFS}
IFS=","
declare -A assoc
while read -r -a array
do
assoc["${array[0]} ${array[1]}"]="${array[#]:2}"
done < data
for key in "${!assoc[#]}"
do
echo "${key} ---> ${assoc[${key}]}"
done
IFS=${oldIFS}
data:
a,b,c,d,e,f
g,h,i,j,k,l
Output:
a b ---> c d e f
g h ---> i j k l
Uses Substring Expansion here ${array[#]:2} to get substring needed as the value of the assoc array. Also added -r to read to prevent backslash to act as an escape character.
Improved based on #gniourf_gniourf's suggestions:
declare -A assoc
while IFS=, read -r -a array
do
((${#array[#]} >= 2)) || continue
assoc["${array[#]:0:2}"]="${array[#]:2}"
done < data
for key in "${!assoc[#]}"
do
echo "${key} ---> ${assoc[${key}]}"
done
I have two arrays A and B. I want to combine them into a new array C, which is their Cartesian product:
A=( 0 1 )
B=( 1 2 )
# Desired output:
C=( 0:1 0:2 1:1 1:2 )
I tried:
for ((z = 0; z <= ${#A[#]}; z++)); do
for ((y = 0; y <= ${#B[#]}; y++)); do
C[$y + $z]="${A[$z]}:"
C[$y + $z + 1]="${B[$y]}"
done
done
But that outputs:
0: : : :
I expected 0:1 0:2 for A = ( 0 ) and B = ( 1 2 ).
If you don't care about having duplicates, or maintaining indexes, then you can concatenate the two arrays in one line with:
NEW=("${OLD1[#]}" "${OLD2[#]}")
Full example:
Unix=('Debian' 'Red hat' 'Ubuntu' 'Suse' 'Fedora' 'UTS' 'OpenLinux');
Shell=('bash' 'csh' 'jsh' 'rsh' 'ksh' 'rc' 'tcsh');
UnixShell=("${Unix[#]}" "${Shell[#]}")
echo ${UnixShell[#]}
echo ${#UnixShell[#]}
Credit: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/
Since Bash supports sparse arrays, it's better to iterate over the array than to use an index based on the size.
a=(0 1); b=(2 3)
i=0
for z in ${a[#]}
do
for y in ${b[#]}
do
c[i++]="$z:$y"
done
done
declare -p c # dump the array
Outputs:
declare -a c='([0]="0:2" [1]="0:3" [2]="1:2" [3]="1:3")'
here's one way
a=(0 1)
b=(1 2)
for((i=0;i<${#a[#]};i++));
do
for ((j=0;j<${#b[#]};j++))
do
c+=(${a[i]}:${b[j]});
done
done
for i in ${c[#]}
do
echo $i
done
Here is how I merged two arrays in Bash:
Example arrays:
AR=(1 2 3)
BR=(4 5 6)
One Liner:
CR=($(echo ${AR[*]}) $(echo ${BR[*]}))
One line statement to merge two arrays in bash:
combine=( `echo ${array1[#]}` `echo ${array2[#]}` )