makefile: reuse multiple recipes - makefile

I want to use the same complex block of recipes for an implicit and a normal rule.
Also, I want make to echo the next command AFTER thre previous command executed.
Make does not allow mixing implicit and normal rules.
Desired output:
$ make foo bar.abc
echo a
a
echo b
b
echo a
a
echo b
b
This won't work:
%.abc foo:
echo a
echo b
This will work:
CMD = echo a && echo b
foo:
$(CMD)
%.abc:
$(CMD)
but the output is not what I want:
$ make foo bar.abc
echo a && echo b
a
b
echo a && echo b
a
b

You can use define to assign multi-line values to variables:
define CMD
echo a
echo b
endef

Related

How to make two echo string in the same line?

How to make two echo strings in the same line?
#!/bin/bash
echo "A"
echo "B" | grep B
result is:
A
B
but I want it to be:
AB
You could avoid the line break in the first echo call with echo -n

Variables from makefile to bash

I have next situation:
source of test.mk:
test_var := test_me
source of test.sh:
$test_var = some method that get test_var from .mk
if [ "$test_var" = "test_me" ] ; then
do something
fi
How can I get variable from .mk file to my .sh file, without grep + sed and other parsing techniques.
EDIT
I can't change .mk file
Create a makefile on the fly to load the test.mk file and print the variable:
value=$(make -f - 2>/dev/null <<\EOF
include test.mk
all:
#echo $(test_var)
EOF
)
echo "The value is $value"
Well if you can't use sed or grep, then you'll have to read the makefile database after parsing using something like:
make -pn -f test.mk > /tmp/make.db.txt 2>/dev/null
while read var assign value; do
if [[ ${var} = 'test_var' ]] && [[ ${assign} = ':=' ]]; then
test_var="$value"
break
fi
done </tmp/make.db.txt
rm -f /tmp/make.db.txt
this makes sure that something like:
value := 12345
test_var := ${value}
will output 12345, instead of ${value}
If you wanted to create variables representing all those from the makefile, you can change the inner if to:
if [[ ${assign} = ':=' ]]; then
# any variables containing . are replaced with _
eval ${var//./_}=\"$value\"
fi
so you will get variables like test_var set to the appropriate value. There are some make variables that start with ., which would need to be replaced with a shell-variable safe value like _, which is what the search-replace is doing.
Create a rule print_var in your makefile with the following code:
print_var:
echo $(test_var)
And in your test.sh, do:
$test_var = $(make print_var)
You also have to consider to put print_var rule in .PHONY section
A variation of #Idelic answer I came up some time ago on my own:
function get_make_var()
{
echo 'unique123:;#echo ${'"$1"'}' |
make -f - -f "$2" --no-print-directory unique123
}
test_var=`get_make_var test_var test.mk`
It uses the lesser known feature of the GNU make - ability to read multiple Makefiles from the command line using multiple -f options.

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

how do i create a variable from the output of another in bash

Here's an example script that doesn't work the way I expect:
#!/bin/bash
for dynamic in a b c; do
myvar=$dynamic
export $myvar="hi"
echo $(eval "$myvar")
echo $dynamic
done
I want the output would be:
hi
a
hi
b
hi
c
Any ideas? I'm willing to stray away from this method, but I definitely want to be able to create a variable named from the output of an algorithm. In this case it's just a for loop.
eval has a tendency to cause bugs, so avoid it whenever possible; in this case it's much cleaner to use indirect expansion with ${!metavariable}:
#!/bin/bash
for dynamic in a b c; do
myvar=$dynamic
export $myvar="hi"
echo ${!myvar}
echo $dynamic
done
The following is the fix for your program. There are two things you got wrong:
The first is you don't need '$' when declaring variables.
The second is that calling eval will treat the content of myvar as a shell script. However you don't have "hi" defined anywhere as a command.
for dynamic in a b c; do
myvar=$dynamic
- export $myvar="hi"
+ export myvar="hi"
- echo $(eval "$myvar")
+ echo "$myvar"
echo $dynamic
done
It's not entirely clear that this is what you're looking for, but I think you want something like:
#!/bin/sh
a=A
b=B
c=C
for i in a b c; do
eval $i=value_$i
eval echo \$$i
done
echo $a # Prints "value_a"

Bash - being space-safe when saving $# in a variable

I have a problem when looping over such a variable. I have prepared 2 examples to show the problem.
ex1:
#!/bin/bash
DIRS="$#"
for DIR in $DIRS; do
echo "$DIR"
done
ex2:
#!/bin/bash
for DIR in "$#"; do
echo "$DIR"
done
The second example works as expected (and required). A quick test follows:
$ ex1 "a b" "c"
a
b
c
$ ex2 "a b" "c"
a b
c
The reason, why I want to use the first method is because I want to be able to pass multiple directories to the program or none to use the current dir. Like so:
[ $# -eq 0 ] && DIRS=`pwd` || DIRS="$#"
So, how do I get example 1 to be space-safe?
Use an array instead of a simple variable.
declare -a DIRS
DIRS=("$#")
for d in "${DIRS[#]}"
do echo "$d"
done
This produces the result:
$ bash xx.sh a "b c" "d e f g" h z
a
b c
d e f g
h
z
$
Why not use the default expansion feature?
for DIR in "${#:-$(pwd)}"; do
echo $DIR
done
One approach is to replace the spaces within arguments with something else, and then substitute spaces back again when you use the argument:
dirs="${#/ /###}"
for dir in $dirs; do
echo "${dir/###/ }"
done
This relies on your being able to come up with some sequence of characters that you can be confident will never appear in a real file name.
For the specific situation you have, where you want to be able to choose between supplying an explicit list of directories or defaulting to the current directory, a better solution is probably to use a function:
do_something() {
for dir in "$#"; do
echo "$dir"
done
}
if [ $# -eq 0 ]; then
do_something .
else
do_something "$#"
fi
Or possibly:
do_something() {
echo "$1"
}
if [ $# -eq 0 ]; then
do_something .
else
for dir in "$#"; do
do_something "$dir"
done
fi

Resources