How to prevent evaluation of string in make / shell? - bash

I have this makefile:
echo:
echo "PASS=$(PASS)"
Which I invoke:
PASS='MYPA$$' make
Which shows me:
echo "PASS=MYPA$"
PASS=MYPA$
Somebody is evaluating $$ -> $.
Is this the shell? Not when inputting the value, since I use single-quotes, preventing the shell to evaluate it.
Maybe the shell invoked by make is doing this ...
Or is it maybe make itself?
How can I avoid it?

On make variables
It's better to think of make variables as macros, than as conventional variables (actually in some versions of make, variables are called macros). The reason is, each time a variable is referenced, it is expanded.
An example from the docs illustrates the standard recursively expanded variables behaviour:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
echo $(foo)
# echoes: Huh?
# `$(foo)' expands to `$(bar)' which expands to `$(ugh)' which finally expands to `Huh?'
If you are using GNU make, one way to avoid further expansion is by using the simply expanded variables:
Simply expanded variables are defined by lines using := (see section Setting Variables). The value of a simply expanded variable is scanned once and for all, expanding any references to other variables and functions, when the variable is defined. The actual value of the simply expanded variable is the result of expanding the text that you write. It does not contain any references to other variables; it contains their values as of the time this variable was defined.
Although simply expanded variables behave more like variables in most programming languages, their sole usage wouldn't solve the problem of environment variables' expansion here because even the first reference var := $(PASS) would expand $$ from the PASS environment variable.
Avoiding expansion of environment variables
We can use the shell function in make to read our environment variable in shell (and not expand it in make):
expanded := $(shell echo "$$PASS")
test:
echo 'PASS=$(expanded)'
echo "PASS=$$PASS"
The shell function will execute echo "$PASS" in shell ($$ is expanded to $ by make when function is executed), and the result (the value of your shell variable PASS) will be stored in the make variable expanded. This variable can now be freely used elsewhere in make, without ever being further expanded.
The only processing make does on the result, before substituting it into the surrounding text, is to convert each newline or carriage-return / newline pair to a single space. It also removes the trailing (carriage-return and) newline, if it's the last thing in the result.
The example above illustrates how to use the make variable expanded and the environment variable PASS in your Makefile script:
$ PASS='MYPA$$' make
echo 'PASS=MYPA$$'
PASS=MYPA$$
echo "PASS=$PASS"
PASS=MYPA$$

Related

why doesn't bash IFS value split expansion argument?

>export FOOBAR=foobar; IFS=b echo ${FOOBAR}
I was expecting to see
foo ar
but I see
foobar
Why?
The IFS hasnt yet taken effect. add another ";":
FOOBAR=foobar IFS=b; echo ${FOOBAR}
In man bash section SIMPLE COMMAND EXPANSION
you can read (abbreviated):
When a simple command is executed
The words that the parser has marked as variable assignments (those preceding the command name) are saved for later processing.
The words that are not variable assignments or redirections are expanded.
...
The text after the = in each variable assignment ... [are] assigned to the variable.
so the IFS=b is done after expanding $FOOBAR.
[edit]I removed the technically incorrect answer.
http://tldp.org/LDP/abs/html/internalvariables.html
"This variable determines how Bash recognizes fields, or word boundaries, when it interprets character strings."

GNU makefile how to and when to quote strings

How and when do I quote a string in a make file? What is best practice?
Is the following the way to quote?
$(warning $(shell ls -ld "$(CURDIR)" ) )
I'm familiar with Bash where you usually quote variables to allow for embedded spaces. Do you do such in a makefile?
How should I do assignment statements with a string?
vara := "$(CURDIR)"
varb := $(CURDIR)
varc := /home/me/source
vard := "/home/me/source"
What about the space after the equal?
You should never quote anything because of make. Make doesn't understand or parse single- or double-quote characters in any way. Every quoting character you write in a makefile will be kept as a literal quote and passed along as-is to the commands that make invokes.
So, if the shell expects and can interpret a quoted string, then you should use quotes. Where the shell doesn't expect or won't correctly interpret a quoted string, you should not use quotes.
In your examples, whether the quotes are acceptable or not depends on how those variables are used. As above, make won't do anything special with quotes, which means that vard (for example) contains the literal string "/home/me/source" (including the quotes).
If you use that value in a way where the shell will handle the quotes for you, then it's fine:
all: ; echo $(vard)
will print /home/me/source (no quotes) because the shell interprets them. But if you use the variable in a make context, for example as a target or a prerequisite:
all: $(vard)
$(vard): ; echo $#
then this is not right, because the target and prerequisite are the literal strings "/home/me/source" (including the quotes).
In general it's best to not use quotes around filenames in variables, and instead add the quotes in the recipe around the make variable. Of course if the variable contains an entire shell script, not just a filename, then you should add appropriate quoting to the script.

Theory: who can explain the use of =

can someone explain me with this code
data=$(date +"%Y-%m-%dS%H:%M:%S")
name="/home/cft/"$data"_test.tar"
touch $name
works, creating a new .tar file but this code doesn't work
data=$(date +"%Y-%m-%dS%H:%M:%S")
name= "/home/cft/"$data"_test.tar"
touch $name
and gives me this error: no such file or directory?
why the space between = and inverted commas creates this error?
Shell allows you to provide per-command environment overrides by prefixing the command with one or more variable assignments.
name= "/home/cft/"$data"_test.tar"
asks the shell to run the program named /home/cft/2013-10-08S12:00:00_test.tar (for example) with the value of name set to the empty string in its environment.
(In your case, the error occurs because the named tar file either doesn't exist or, if it does, is not an executable file.)
A variable assignment is identified by having no whitespace after the equal sign.
(name = whatever, of course, is simply a command called name with two string arguments, = and whatever.)
You can't have whitespace between the equal sign and the definition.
http://www.tldp.org/LDP/abs/html/varassignment.html
There is no theory behind this. It's just a decision the language designers made, and which the parser enforces.
In BASH (and other Bourne type shells like zsh and Kornshell), the equal sign cannot have spaces around it when setting variables.
Good:
$ foo="bar"
Bad:
$ foo= "bar"
$ foo = "bar"
There's no real reason that would prevent spaces from being used. Other programming languages have no problems with this. It's just the syntax of the shell itself.
The reason might be related to the original Bourne shell parsing where the shell would break up a command line based upon whitespace. That would make foo=bar a single parameter instead of two or three (depending if you have white space on both sides or just one side of the equal sign). The shell could see the = sign, and know this parameter is an assignment.
The shell parameter parsing is very primitive in many ways. Whitespace is very important. The shell has to be small and fast in order to be responsive. That means stripping down unessential things like complex line parsing.
Inverted commas I believe you mean quotation marks. Double quotes are used to override the breaking out of parameters over white space:
Bad:
$ foo=this is a test
bash: is: command not found
Good:
$ foo="this is a test"
Double quotes allow interpolation. Single quotes don't:
$ foo="bar"
$ echo "The value of foo is $foo"
The value of foo is bar
$ echo 'The value of foo is $foo'
The value of foo is $foo.
If you start out with single quotes, you can put double quotes inside. If you have single quotes, you can put double quotes inside.
$ foo="bar"
$ echo "The value of foo is '$foo'"
The value of foo is 'bar'
$ echo 'The value of foo is "$foo"'
The value of foo is "$foo"
This means you didn't have to unquote $data. However, you would have to put curly braces around it because underscores are legal characters in variable names. Thus, you want to make sure that the shell understand that the variable is $data and not $data_backup:
name="/home/cft/${data}_test.tar"

How do I expand variables in a bash variable without expanding wildcard?

I have a variable that contains this kind of string :
var='$FOO/bar/baz*'
and I want to replace the variable $FOO by its content. However, when i do
var=$(eval "echo $var")
The variable is replaced, but the star is also replaced so that var now contains every possible match in my filesystem (as if i pressed tab in a shell).
for example, if $FOO contains /home, var will contain "/home/bar/baz1.sh /home/bar/baz2.sh /home/bar/baz.conf"
How do i replace the variable without expanding wildcards ?
Turn off globbing in bash, then reenable it.
set -f
var="$FOO/bar/baz*"
set +f
Just drop the quotes:
var=$FOO/bar/baz/*
Globs are not expanded on the RHS of a variable assignment.

Why are shell script variables declared without a preceding `$`?

I noticed that in shell script when we declare a variable, the preceding dollar sign is not needed, although when we want to access this variable later we should add a dollar sign in front of this variable name.
just like:
#!/bin/sh
VAR_1=Hello
VAR_2=Unix
echo "$VAR_1 $VAR_2"
This is different from other languages, like Perl we will always have the preceding dollar sign with the variable name, I just want to know any good reason for shell script to do it in this way, or it's just a convention...?
Shell is a different language than Perl is a different language than C++ is a different language than Python. You can add "with different rules" to each of the languages.
In shell an identifier like VAR_1 names a variable, the dollar sign is used to invoke expansion. $var is replaced with var's content; ${var:-foo} is replaced with var's content if it is set and with the word foo if the variable isn't set. Expansion works on non-variables as well, e.g. you can chain expansion like ${${var##*/}%.*} should leave only a file base name if var contains a file name with full path and extension.
In Perl the sigil in front of the variable tells Perl how to interpret the identifier: $var is a scalar, #var an array, %var a hash etc.
In Ruby the sigil in front of the varible tells Ruby its scope: var is a local variable, $var is a global one, #var is an instance variable of an object and ##var is a class variable.
In C++ we don't have sigils in front of variable names.
Etc.
In the shell, the $ sign is not part of the variable name. It just tells the shell to replace the following word with the contents of the variable with the same name, i.e. $foo means "insert the contents of the variable foo here".
This is not used when assigning to the variable because there you explicitly don't want to insert the old contents; you want to use the variable itself (in some ways this is similar to dereferencing pointers).
It's basically a syntactical convention.
DOS/.bat file syntax works the same way.
1) to create a variable, no metacharacter.
2) to "dereference" the contents of the variable, use the metacharacter.
DOS:
set VAR=123
echo %VAR%

Resources