What does $$i do in this makefile loop? - makefile

I have a simple loop in my makefile:
# for i in $(APPDIRS) ; do \
$(MAKE) -C $$i real_clean ; \
done
I know $$ means process id of the script itself. What does the 'i' do? Am I iterating through the process id with the for loop?

$ is used both as the variable identifier for make and for the shell. So if you want the shell to expand a variable, rather than having the makefile do it, you need to escape the $ by using $$.
As a quick example, let's assume APPDIRS=a b c d e f and MAKE=make (and i unset in the makefile). Then your shell sees:
for i in a b c d e f ; do
make -C $i real_clean;
done
With a $ in place of the $$, it would see:
for i in a b c d e f ; do
make -C real_lean;
done
Which is definitely not what you want.

Related

How to pass items to bash for loop on multiple lines?

I can do this in bash:
for n in a b c d e ; do
echo $n
done
If a, b, c, d, e turn out to be long lines, without using a separate variable, how do I put them each on a separate line in the for loop syntax?
Split the line with \:
$ for i in a \
> b \
> c \
> d ; do echo $i ; done
a
b
c
d
I'd use a "here document":
while read n; do
echo $n
done <<EOF
some detailed stuff here
other things on the next line, blah blah blah
EOF
Of course in this particular example you can replace the entire while loop with cat but I suppose your real code is more involved.
you can put a b c d e in a file and run a loop over that file.
while read line
do
echo "$line \n"
done < file

Bash pass all arguments as one quoted argument to another command

I usually do this:
git commit -m "My hands are typing words!"
I am gettin' tired of that, so I made this batch:
#echo off
set var=%*
git commit -m "%var%"
Which works as:
commit.bat blah blah blah
So I can drop the -m and the quotes, but it adds .bat. When I remove file extension, git Bash tries to interpret the batch as bash. So I need to use bash syntax instead. That's fine, I tried this:
#!/usr/bin/bash
git commit -m "$#"
That doesn't work, it passes arguments as multiple arguments. It invokes this:
git commit -m My hands are typing words!
I tried to add even more quotes (git commit -m ""$#""), no effect.
So how do I convert all arguments to a string that can be passed in bash to another command?
You can use "$*" instead of "$#".
This sample script should explain it:
$ cat a.sh
#!/bin/bash
echo '$*:'
printf "%s\n" $*
echo
echo '"$*":'
printf "%s\n" "$*"
echo
echo '$#:'
printf "%s\n" $#
echo
echo '"$#":'
printf "%s\n" "$#"
$ ./a.sh a b "c d" e
$*:
a
b
c
d
e
"$*":
a b c d e
$#:
a
b
c
d
e
"$#":
a
b
c d
e
But correctly, you should use "$1" & quote your message string before passing to the script.
In above example, you can see that the spaces between c & d are retained, but those between "c d" & e are lost.

Bash: for in loop with brace expansion in list

I would like to write a for in loop in which the possible values are taken from another variable. The part that I can't figure out is how to include values that need brace expansion. For example:
TEXT="a b c d{a..c} e f"
for VAR in $TEXT; do echo $VAR; done
What i get is
a
b
c
d{a..c}
e
f
But what I want is
a
b
c
da
db
dc
e
f
Does anyone know how I can get this to work?
Thank you very much!
I fear that you may have to resort to using eval in this case...However, I'd suggest that you avoided using a loop:
eval "printf '%s\n' $TEXT"
Using eval means that the braces will be expanded, producing your desired outcome.
You can see what happens using set -x:
$ set -x
$ eval "printf '%s\n' $TEXT"
+ eval 'printf '\''%s\n'\'' a b c d{a..c} e f'
++ printf '%s\n' a b c da db dc e f
a
b
c
da
db
dc
e
f
Note that the expansion occurs before the list is passed as arguments to printf.
If you get to define $TEXT yourself and want to loop through each of its expanded elements, I'd suggest using an array instead:
text=( a b c d{a..c} e f )
for var in "${text[#]}"; do
# whatever with "$var"
done
This is the preferred solution as it doesn't involve using eval.
EDIT
While there's a time & place for eval, it's not the safest approach for the general case. See Why should eval be avoided in Bash, and what should I use instead? for some reasons why.
Use eval:
TEXT="a b c d{a..c} e f"
for VAR in $TEXT; do eval echo $VAR; done
Output:
a
b
c
da db dc
e
f
If you want to loop over every element, you'll have to nest your loops:
for VAR in $TEXT; do
for VAR2 in $(eval echo $VAR); do
echo $VAR2
done
done
Output:
a
b
c
da
db
dc
e
f
Or use the eval on TEXT rather than each element:
TEXT="$(eval echo "a b c d{a..c} e f")"
for VAR in $TEXT; do echo $VAR; done
Output:
a
b
c
da
db
dc
e
f
Without eval, but with command substitution:
$ text="$(echo a b c d{a..c} e f)"
$ echo "$text"
a b c da db dc e f
And, as mentioned in Tom's answer, an array is probably the better way to go here.

What can change command substitution behavior in bash?

One of my bash shells is giving me different behavior than the others. I don't know what setting has changed, but check this out:
hersh#huey$ for f in $(echo a b c); do echo foo $f; done
foo a
foo b
foo c
hersh#huey$ logout
Connection to huey closed.
hersh#spf$ for f in $(echo a b c); do echo foo $f; done
foo a b c
On host huey, the "for" loop over did what I expected: it split on word boundaries. On host spf, the "for" loop did not split on word boundaries, it treated the whole thing as a single item. Similarly if I use $(ls) it will treat the whole big multi-line thing as a single item and print "foo" just in front of the first line.
On both hosts I'm running bash. In other bash shells on host spf it also works normally (splits on word boundaries). What could change that? Both are running the same version of bash (which is "4.2.25(1)-release (x86_64-pc-linux-gnu)"). The characters are the same, since I copy-pasted the for-loop command line with the mouse from the same source both times.
Thanks!
Check your $IFS
mogul#linuxine:~$ for f in $(echo a b c); do echo foo $f; done
foo a
foo b
foo c
mogul#linuxine:~$ export IFS=SOMETHINGBAD
mogul#linuxine:~$ for f in $(echo a b c); do echo foo $f; done
foo a b c
mogul#linuxine:~$
and have a look at the manual over here bash Word Splitting

getting positional arguments in bash

I have a bash script called foo with variable number of arguments, with the first one being a required one, i.e.:
foo a1 b2 b3 b4 ...
I understand that in bash $1 will get me argument a1, but is there a way to get all the rest of the arguments? $# or $* seem to get me all the arguments including a1.
Slice the $# array.
echo "${#:2}"
You can use shift command for that. That will remove $1 and you can access the rest of arguments starting with $1.
#!/bin/sh
echo $*
shift
echo $*
shift will shift all the parameters, running the previous example would give:
$ test_shift.sh a b c d e
a b c d e
b c d e
./foo.sh 1 2 3 4
#!/bin/bash
echo $1;
echo $2;
echo $3;
echo $4;
Will output:
1
2
3
4

Resources