I'm writing a simple script to generate all combinations of a and b of a given length (say 10). I want to be able to do this on a command line (I know this is fairly easy if I just put everything in a bash script file and execute it). However, I was wondering if it's possible to do without any extra files. Here's what I have so far:
n=10;
for i in `seq 1 1 $n`; do
echo "for a$i in {a..b}; do ";
done;
echo -n "echo ";
for i in `seq 1 1 $n`; do
echo -n '$'"a$i"; done;
echo;
for i in `seq 1 1 $n`; do
echo "done;";
done
(I formatted the code for readability, but it's actually all on one line run from a prompt)
This gives me the following output:
for a1 in {a..b}; do
for a2 in {a..b}; do
for a3 in {a..b}; do
for a4 in {a..b}; do
for a5 in {a..b}; do
for a6 in {a..b}; do
for a7 in {a..b}; do
for a8 in {a..b}; do
for a9 in {a..b}; do
for a10 in {a..b}; do
echo $a1$a2$a3$a4$a5$a6$a7$a8$a9$a10
done;
done;
done;
done;
done;
done;
done;
done;
done;
done;
which is just fine. If I copy that and paste it back on the command line, it works like a charm and gives me the result.
The question is how do I do this with just the initial script, without copy-pasting and without redirecting anything to files.
I've tried sticking $( ) around the script, but that gives me "No command 'for' found'", since it's not really a command but a bash builtin. I've tried putting eval somewhere before this, but I just keep getting more errors. I'm a bit stuck, so any help would be greatly appreciated.
(Btw, just to reiterate, I'm doing this more or less to just learn bash more -- that's why I don't want to redirect the output to a file and then execute that file. I know how to do that part, but I don't know how to just do it from command line)
You need to use an eval, $() gives you a string.
eval $( echo echo foo )
Another option is to stick into a subshell and pipe it to a bash:
(echo echo foo) | /bin/bash
You can do for i in $(seq $n) instead of seq 1 1 $n.
You can do for ((i=1; i<=$n; i++)) and avoid calling an external utility.
You can do this (slightly hacky with only one loop):
$ a=A; b=B; n=4; s=''; for ((i=1;i<=n;i++)); do s+="{$a..$b}"; done; eval echo "''" $s"$'\n'"
or this (highly hacky without any loops):
$ a=A; b=B; n=4; eval echo "''" $(printf "{$a..$b}%.0s" $(eval echo "{1..$n}"))"$'\n'"
Either one will get you this:
AAAA
AAAB
AABA
AABB
ABAA
ABAB
ABBA
ABBB
BAAA
BAAB
BABA
BABB
BBAA
BBAB
BBBA
BBBB
Related
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 have a file that contains information in two columns:
box1 a1
box2 a2
I'm trying to read this file line by line into read and have each line items be put into a variable.
On the first pass, $a would contain box1 and $b would contain a1.
On the second pass, $a would contain box2 and $b would contain a2, etc.
An example of the code that I am using to try to achieve is this:
for i in text.txt; do
while read line; do
echo $line | read a b;
done < text.txt;
echo $a $b;
done
This gives me the following results:
box1 a1 box2 a2
When I expected the following results:
box1 a1
box2 a1
How can I fix this?
Piping into a read command causes the variables to be set in a subshell, which makes them inaccessible (indeed, they are gone) to the rest of your code. In this case, though, you don't even need the for loop or the second read command:
while read -r a b; do
echo "$a" "$b"
done < text.txt
To store the variables into the current shell:
~$ {
read -r a
read -r b
read -r c
} < <(printf '%s\n' one two three)
$ echo "a=$a b=$b c=$c"
a=one b=two c=three
Another approach
echo -e "one\ntwo\nthree" | {
read -r a;
read -r b;
read -r c;
echo "output of ($a, $b, $c)"
}
I have this code. But I always get an error line 34: [24: command not found.
It should execute the code in the if statement as soon as the do while loop executet the 24 time.
#!/bin/bash
input="./user.cvs"
latexStart="\\documentclass[12pt]{article}\\usepackage{labels}\\usepackage{graphicx}\\usepackage{array}\\begin{document}\\graphicspath{{./QRcodes/}}\\newcolumntype{C}{>{\\centering\\arraybackslash} m{27mm} }"
latexEnd="\\end{document}"
latexBeginLabels="\\begin{labels}"
latexEndLabels="\\end{labels}"
counter=0
touch newLabels.txt
echo "$latexStart" >> newLabels.txt
echo "$latexBeginLabels" >> newLabels.txt
while IFS=';' read -r f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13
do
path="./QRcodes/$f2$f3.png"
vcard="BEGIN:VCARD%0AN;CHARSET=utf-8:$f3;$f2;;$f1;%0AADR;CHARSET=utf-8;INTL;PARCEL;WORK:;;$f10;$f11;;$f12;$f13%0AEMAIL;INTERNET:$f6%0AORG:$f4%0ATEL;WORK:$f8%0ATEL;FAX;WORK:$f9%0ATITLE:$f5%0AURL;WORK:$f7%0AEND:VCARD"
encodedVCard=$(echo "$vcard" | sed -e 's/\+/\%2B/g')
url="http://api.qrserver.com/v1/create-qr-code/?size=300x300&data=$encodedVCard"
wget -O "$path" "$url"
if ["${counter:-0}" -gt 21] ;
then
counter=0
echo "$latexEndLabels" >> newLabels.txt
echo "\\newpage" >> newLabels.txt
echo "$latexBeginLabels" >> newLabels.txt
fi
echo "\\begin{tabular}{ C C } \\includegraphics[height=30mm]{name.png} & Name Man \\\\ \\end{tabular}" >> newLabels.txt
let counter=counter+1
done < "$input"
echo "$latexEndLabels" >> newLabels.txt
echo "$latexEnd" >> newLabels.txt
The error is in Line 34, if ["${counter:-0}" -gt 21] ;. I got this example from Compare integer in bash, unary operator expected
What am I doing wrong?
You need
if [ "${counter:-0}" -gt 21 ]; then ...
i.e. your conditional statement is separated from the surrounding square brackets by spaces.
I'm quoting counter to be safe, but if you're sure it'll never be empty then you can skip that. It's probably better to be safe than sorry, however, and follow your original pattern.
I need to read some configuration data into environment variables in a bash script.
The "obvious" (but incorrect) pattern is:
egrep "pattern" config-file.cfg | read VAR1 VAR2 VAR3 etc...
This fails because the read is run in a subshell and therefore cannot set variables in the invoking shell. So I came up with this as an alternative
coproc egrep "pattern" config-file.cfg
read -u ${COPROC[0]} VAR1 VAR2 VAR3 etc...
which works fine.
To test what happens if the coprocess returns more than one line, I tried this:
coproc cat config-file.cfg
read -u ${COPROC[0]} VAR1 VAR2 VAR3 etc...
where config-file.cfg contains three lines.
$ cat config-file.cfg
LINE1 A1 B1 C1
LINE2 A2 B2 C2
LINE3 A3 B3 C3
I expected this to process the first line in the file, followed by some kind of "broken pipe" error message. While it did process the first line, there was no error message and no coprocess was left running.
So I then tried the following in a script:
$ cat test.sh
coproc cat config-file.cfg
read -u ${COPROC[0]} VAR1 VAR2 VAR3 VAR4
echo $VAR1 $VAR2 $VAR3 $VAR4
wait
echo $?
Running it:
$ bash -x test.sh
+ read -u 63 VAR1 VAR2 VAR3 VAR4
+ cat config-file.cfg
LINE1 A1 B1 C1
+ wait
+ echo 0
0
Where did the remaining two lines go? I would have expected either "broken pipe", or the wait to hang since there was nothing to read the remaining lines, but as you can see the return code was zero.
As per comments above, you can use process substitution to achieve just that. This way, read is not run in a subshell and the captured vars will be available within the current shell.
read VAR1 VAR2 VAR3 < <(egrep "pattern" config-file.cfg)
"If the <(list) form is used, the file passed as an argument should be read to obtain the output of list" -- what "file passed as an agrument" are they talking about?
That is rather cryptic to me too. The chapter on process substitution in Advanced Bash-scripting Guide has a more comprehensive explanation.
The way I see it, when the <(cmd) syntax is used, the ouput of cmd is made available via a named pipe (or temp file) and the syntax is replaced by the filename of the pipe/file. So for the example above, it would end up being equivalent to:
read VAR1 VAR2 VAR3 < /dev/fd/63
where /dev/fd/63 is the named pipe connected to the stdout of cmd.
If I understand correctly your question (and I hope I'm not stating the obvious),
read reads one line at a time, as in:
$ read a b c < config-file.cfg && echo $?
0
or:
$ printf '%s\n%s\n' one two | { read; echo "$REPLY";}
one
$ echo ${PIPESTATUS[#]}
0 0
To read all the input you'll need a loop:
$ coproc cat config-file.cfg
[1] 3460
$ while read -u ${COPROC[0]} VAR1 VAR2 VAR3; do echo $VAR1 $VAR2 $VAR3; done
LINE1 A1 B1 C1
LINE2 A2 B2 C2
LINE3 A3 B3 C3
[1]+ Done coproc COPROC cat config-file.cfg
Just to add that this is explained in the FAQ.
What happens is, as soon as the subshell finishes, the parent shell cleans up and closes the FDs. You're lucky you even got to read the first line!
Try this in an interactive shell:
$ coproc ECHO { echo foo; echo bar; }
[2] 16472
[2]+ Done coproc ECHO { echo foo; echo bar; }
$ read -u ${ECHO[0]}; echo $REPLY
bash: read: -u: option requires an argument
read: usage: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
It even mops up the environment variable.
Now try this:
$ coproc ECHO { echo foo; echo bar; sleep 30; }
[2] 16485
$ read -u ${ECHO[0]}; echo $REPLY
foo
$ read -u ${ECHO[0]}; echo $REPLY
bar
$ read -u ${ECHO[0]}; echo $REPLY # blocks until the 30 seconds are up
[2]+ Done coproc ECHO { echo foo; echo bar; sleep 30; }
As for solving the problem behind the question: Yes, redirection and process substitution is the better choice for the particular example given.
I have a problem updating a value of a variable in a shell script from inside of a while loop. It can be simulated with the following piece of code:
printf "aaa\nbbb\n" | \
while read x ; do
y=$x
echo "INSIDE: $y"
done
echo "OUTSIDE: $y"
Output:
INSIDE: aaa
INSIDE: bbb
OUTSIDE:
Here printf command just display two lines, while-read loop read it line by line, updating certain variable, but as soon as control going out of the loop the value of the variable gets lost.
I guess the problem is related to the fact that 'pipe-while-read' statement causes shell to execute the body of the loop in a subprocess, which cannot update the shell variables in the main loop.
If I rewrite the first two lines of code as
for x in `printf "aaa\nbbb\n" ` ; do
Output:
INSIDE: aaa
INSIDE: bbb
OUTSIDE: bbb
It could be a workaround, but not for my case because in reality I have not 'aaa' and 'bbb' but more complex strings including whitespaces etc.
Any idea how to tackle the problem, namely: read a command output line by line in a loop and be able to update shell variables?
Thanks.
An excerpt from man bash:
Each command in a pipeline is executed as a separate process (i.e., in a subshell).
And Subshell cannot change the variable in Parent.
One of the possible Solution is:
IFS='\n'
while read x ; do
y=${x}
echo "INSIDE: ${y}"
done <<EOT
aaa
bbb
EOT
echo "OUTSIDE: ${y}"
Or if the input is a file:
IFS='\n'
while read x ; do
y=${x}
echo "INSIDE: ${y}"
done < /path/to/file
echo "OUTSIDE: ${y}"
This reads one line at a time, and doesn't have any issue with spaces.
You can get rid of the pipe-into-while by using process substitution instead:
while read x ; do
y=$x
echo "INSIDE: $y"
done < <(printf "aaa\nbbb\n")
echo "OUTSIDE: $y"
Alternatively, if your input is in a file, you can redirect it into while:
while read x ; do
y=$x
echo "INSIDE: $y"
done < file
echo "OUTSIDE: $y"