I have a few python scripts, and for various reasons, I have shell script wrapper around them:
#!/bin/sh
source env.sh
python $0.py $#
This works fine, unless the arguments needs to be quoted. In this case, of course, the wrapper, eats the quotes, and gives the un-quoted version to the python script. So, my first question is "How can I get sh to not eat the quotes?"
However, even if I back-slash the quotes, it doesn't work. I print out the entire command I'm about to call:
source env.sh
echo "python $0.py $#"
python $0.py $#
If I call it with foo \"a b c" it outputs
python foo.py "a b c"
However, when foo.py gets called, it still GETS foo.py a b c
If I just copy and paste the output, and run it, it runs fine.
Can anyone tell me why the actual execution would fail from the script, but succeed on the command line?
thanks.
you need quotes around the actual use of the $# parameter, i.e.
source env.sh
echo "python $0.py $#"
python $0.py "$#"
I hope this helps.
Related
In a Makefile, reading user input terminated by <ENTER> can be implemented by using (the shell function) read. On the shell (bash), reading a single character can be done with read -n 1. However, I was surprised that read -n 1 didn't seem to work with GNU Make 4.2.1. Do I miss some escaping here?
Bash:
$> echo x | read -n 1 mychar; echo 'you typed '$mychar
you typed x
Makefile:
all:
read -n 1 mychar; echo 'you typed '$$mychar
Make:
$> echo x | make
read -n 1 mychar; echo 'you typed '$mychar
/bin/sh: 1: read: Illegal option -n
Does make provide its own version of read ?
System: GNU Make 4.2.1, Ubuntu 20.04.3 LTS
PS: I am aware that user interaction is considered bad style, and it clearly should not be used in configuration workflows. However, it comes in quite handy for targets associated with pruning, resetting or deleting data (e.g., make clear). And asking for y/N confirmation gives you a chance to tell your users what it will take them to rebuild what they are going to remove.
PPS: I know that the regular read gives you almost the same functionality, except that you need to hit <ENTER>. This question about getting a better understanding for the relationship between make and its shell enviroment.
NB: This problem is different from Makefile - Why is the read command not reading the user input?. They just didn't properly escape the variable.
I have a Makefile similar to the following:
target1: DEFAULT_VALUE ?= $(shell bash -c 'read -p "Enter DEFAULT_VALUE to set: " value && echo $$value')
target2:
echo "Hello"
target1:
echo "World"
I expect that the code to set DEFAULT_VALUE will only execute if I run make target1, however I find that it runs even if I run make target2
Does anyone know why this happens?
Your "similar" makefile is not similar enough. Your example above works fine for me: if I run make target2 then the shell command is not executed. If I run make target1, then it is.
Please check your example before posting it here and provide one that really fails.
My suspicion is that in your real environment, whatever is represented by target2 is a prerequisite of whatever is represented by target1, which means that target2 will inherit all of target1's target-specific variable assignments.
With the above Makefile, the shell command will never run, for any target. That's because that style of variable is a recursively expanding variable, so it will be expanded (and the shell command run) every time the variable is used.
If you change the last action in the Makefile to
target1:
echo $(DEFAULT_VALUE)
echo $(DEFAULT_VALUE)
then it will run TWICE when you make target1, echoing potentially two different things
If you want the shell command to run only once, you need to use := to set it. But if you do that, it will be run when the Makefile is read (before its even considering which targets to build), so it will run regardless of which target you eventually specify.
If you want something that will only run when a given target is built, you need to put it in the actions for that target. The easiest way to do that is with a recursive make call
target1:
read -p "Enter DEFAULT_VALUE to set: " value && \
$(MAKE) real_target1 DEFAULT_VALUE=$$value
I have a collection of examples that I want to make sure they fail to compile. What is the best way to to that with a *GNU Makefile?
test_nocompile: $(NOCOMPILE_CPP)
for cpp in $(NOCOMPILE_CPP) ; do \
echo === $$cpp === ; \
if $(CXX) $(CXXFLAGS) -c -o fail.o $$cpp ; then echo ok ; else exit 1; fi ; \
done
As you can see, I have several difficulties here:
Accessing the shell-for variable cpp: $cpp, \$cpp and $$cpp both do not work.
Even with my if the make stops after the first g++-compile fails. But thats exactly what I want. I want failing to g++-compile to be considered the correct behaviour.
Shell-for-loops in Makefiles are not the best idea. Is there a better way? I can not think of one, because since I expect the compiles to fail, I do not have a correct target, right?
Advanced and optional:
If I could manage to have the fail-check above working, I could try a second pass of compilation, this time with an additional -DEXCLUDE_FAIL, which takes out the offending line from my examples, and then the code should compile. Any idea?
or should write a Python script for that...? ;-)
Edit:
I think my "advanced and optional" gave me a very good idea right now. I could use makes dependency checking again. Just a rough sketch here:
NOCOMPILE_CPP := $(wildcard nocompile/ *.cpp)
NOCOMPILE_XFAILS := $(addsuffix .xfail,$(basename $(NOCOMPILE_CPP)))
%.xfail: %.cpp
if $(CXX) $(CXXFLAGS) -o $# $< ; then exit 1 ; else echo OK ; fi
$(CXX) $(CXXFLAGS) -DEXCLUDE_FAILCODE -o $# $<
test_nocompile: $(NOCOMPILE_XFAILS)
Is this elegant? Then I only have to work out how -DEXCLUDE_FAILCODE can make the failing tests work.... Non-trivial, but doable. I think that could do it, right?
Works for me. What do you get in echo?
You need to negate the condition in if then. Right now it quits when the file doesn't compile and AFAIU you need the opposite.
Why it's not a good idea? But you can write a script that will call $(CXX) and return a proper error code (0 if it doesn't compile). Then you may have normal targets with this script. I'm not very good with specifics of GNU make, probably it's possible with the builtin stuff.
Advanced & optional:
1. Let's first make the thing work:)
2. Personally i don't use python, therefore don't see a need here =:P
It seems as if a script with #! prefix can have the interpreter name and ONLY one argument. Thus:
#!/bin/ls -l
works, but
#!/usr/bin/env ls -l
doesn't
Do you agree? Any thoughts?
Francesc
Different Unixes interpret #! differently. Here's a comprehensive-looking writeup: http://www.in-ulm.de/~mascheck/various/shebang/
It seems that the lowest common denominator across platforms is "the interpreter (which must not itself be a script) and no more than one argument".
Originally, we only had one shell on Unix. When you asked to run a command, the shell would attempt to invoke one of the exec() system calls on it. It the command was an executable, the exec would succeed and the command would run. If the exec() failed, the shell would not give up, instead it would try to interpret the command file as if it were a shell script.
Then unix got more shells and the situation became confused. Most folks would write scripts in one shell and type commands in another. And each shell had differing rules for feeding scripts to an interpreter.
This is when the “#! /” trick was invented. The idea was to let the kernel’s exec () system calls succeed with shell scripts. When the kernel tries to exec () a file, it looks at the first 4 bytes which represent an integer called a magic number. This tells the kernel if it should try to run the file or not. So “#! /” was added to magic numbers that the kernel knows and it was extended to actually be able to run shell scripts by itself. But some people could not type “#! /”, they kept leaving the space out. So the kernel was expended a bit again to allow “#!/” to work as a special 3 byte magic number.
I created a configure.ac file like this:
AC_INIT()
set
the purpose of this is to print every available environment variable the configure script creates using set, so I do this:
user#host:~$ autoconf
user#host:~$ ./configure
which prints a bunch of variables like
build=
cache_file=/dev/null
IFS='
'
LANG=C
LANGUAGE=C
datarootdir='${prefix}/share'
mandir='${datarootdir}/man'
no_create=
So far so good.
The problem is:
I want to expand the variables like ${prefix}/share - but piping
everything to a file example.sh and executing it using bash doesn't work, because bash complains about modifying read-only variables like UID and expansion itself doesn't seem to work either.
I tried using a makefile for this where expansion works, but it complains about newlines in strings, like in the above output the line IFS=' causes an error message Makefile:24: *** missing separator. Stop.
Does anyone have an idea how to get a fully expanded version of configure's output?
The Autoconf manual (I cannot recall or find exactly where) recommends to "manually" do such a kind of variable substitution from within a Makefile.in (or .am if you happen to use Automake):
Makefile.in
[...]
foo.sh: foo.sh.in
$(SED) \
-e 's|[#]prefix#|$(prefix)|g' \
-e 's|[#]exec_prefix#|$(exec_prefix)|g' \
-e 's|[#]bindir#|$(bindir)|g' \
-e 's|[#]datarootdir#|$(datarootdir)|g' \
< "$<" > "$#"
[...]
foo.sh.in
#!/bin/sh
datarootdir=#datarootdir#
du "$datarootdir"