Makefile syntax $(A,B,C)? - makefile

Consider the following code:
$ANIMAL = COW PIG CHICKEN VAMPIRE
all:
#echo $(ANIMAL, F, >.txt)
I strove to find a section in GNU make manual that mentions the above syntax, but I couldn't find anything related to it. What does it print and how is the syntax structured for the functionality?
Added: When a line starts with "#--" what does it mean?
#-- $(GEN_ENV); ...

To answer your addition: In regular Makefiles (read: POSIX, GNU, ...)
a leading '#' supresses echoing of the command.
a leading '-' says to ignore a non-zero exit status
both can be combined, and repetitions are okay, so #---###-#---echo foo is the same as #-echo foo

This is called "macro modifiers". This is not a GNU make feature. Take a look at this chapter of OPUS make tutorial. The general syntax of these modifiers:
$(name,modifier[,modifier]...)
name is macro expanded, then each modifier is applied in succession to the elements of the expanded value.
Take a look then at the list of modifiers and it becomes clear that it forms a list of file names (truncates paths of each variable in ANIMAL) with .txt added. So, in your case it shoud output:
COW.txt PIG.txt CHICKEN.txt VAMPIRE.txt
PS
I looked through the reference mentioned above and don't think the first line ($ANIMAL = ) is correct since macro definition should start without $.

Based on your comments it seems you are actually using OpusMake, rather than GNU make. You can find more information about it on the Opus Software, Inc. website, and also in this handy reference guide. From those sources you can see that you have an example of a macro employing macro modifiers in its expansion.
Generally speaking $(FOO) is expanded to the unmodified value of the variable FOO, while $(FOO,mod1[,mod2[,...]]]) expands to the value of FOO, modified according to the modifiers you specify. Note that you can string together any number of modifiers, and they will be applied in left-to-right order.
There's a ton of possible modifiers, but your example specifically uses two:
The F modifier, which means "use just the final path component of each pathname in the variable value"
The >str modifier, which means "append the text str to each space-separated word in the value".
Here's a quick example:
FOO=abc/def ghi/jkl
BAR=$(FOO,F)
BAZ=$(FOO,>.txt)
BOO=$(FOO,F,>.txt)
BAR will have the value def jkl (ie, just the filename portion of each path).
BAZ will have the value abc/def.txt ghi/jkl.txt (ie, append .txt to each space-separated word in the value)
BOO will have the value def.txt jkl.txt (ie, first take just the filename portion of each path, then append .txt to each)

Related

what does $(1:D=) mean?

I'm reading Jamrule file of some project to understand how it builds.
But there is some that I can't understand.
Such like $(1:D=) or $(1:S=$(sample)) or $(1:G=$(sample))
what does it mean?
I searched colon and equal meaning in a shell script but I couldn't find when the alphabet is in between them.
ex) local _s = $(1:D=) ;
$(1) expands the first argument of a rule. $(1:D=foo) applies a modifier that replaces the directory portion of the expanded elements (dirname, if you think in shell terms) with the string foo. The special case $(1:D=) removes the directory portion. The modifier S refers to the suffix (aka extension) of the file name, G to the "grist" of a jam target name.
Please refer to the Variable Expansion section of the Perforce Jam documentation for a complete list. I can recommend reading the complete Jam documentation to understand the specific concepts (like grist).

parameter expansion using bang dollar (`!$`)

Is there any way to use !$ in a parameter expansion context? The desired usage that motivates this question is rapid (in terms of key strokes) alteration of the name of a file (e.g., instead of saving the file name in a variable and executing rsvg-convert $svg > ${svg/.svg/.png}, one could instead use rsvg-convert $! > $!{/.svg/.png}, where $!{/.svg/.png} is erroneous syntax intimating the desired effect; when the file in question was the last token on the preceding line, such a command can often be typed more quickly than alternatives like using tab completion in the presence of files sharing prefixes of varying length, or copying and pasting the file name by selecting with a mouse). As far as I can tell, there is no way to employ !$ in such a context, but perhaps through some chicanery a similar effect could be achieved.
Depending on how sophisticated you want the substitution, history expansion does support replacing the first occurrence of a string with another. You just precede the substitution with : like:
rsvg-convert !$ > !$:s/.svg/.png
You can see all the history modifiers here
At least in emacs-mode bash will also put the last argument of the previous command inline (not for expansion when you run the command) if you press alt+.. So in this case it might be fastest to type:
rsvg-convert
then alt+.>alt+. then delete the extension it just put in place with alt+bksp then the new extension: png
If you look further into the modifiers in Eric's example, you could also do:
rsvg-convert !$ > !$:r.png
Assuming .svg is a suffix of course

Removing an optional / (directory separator) in Bash

I have a Bash script that takes in a directory as a parameter, and after some processing will do some output based on the files in that directory.
The command would be like the following, where dir is a directory with the following structure inside
dir/foo
dir/bob
dir/haha
dir/bar
dir/sub-dir
dir/sub-dir/joe
> myscript ~/files/stuff/dir
After some processing, I'd like the output to be something like this
foo
bar
sub-dir/joe
The code I have to remove the path passed in is the following:
shopt -s extglob
for file in $files ; do
filename=${file#${1}?(/)}
This gets me to the following, but for some reason the optional / is not being taken care of. Thus, my output looks like this:
/foo
/bar
/sub-dir/joe
The reason I'm making it optional is because if the user runs the command
> myscript ~/files/stuff/dir/
I want it to still work. And, as it stands, if I run that command with the trailing slash, it outputs as desired.
So, why does my ?(/) not work? Based on everything I've read, that should be the right syntax, and I've tried a few other variations as well, all to no avail.
Thanks.
that other guy's helpful answer solves your immediate problem, but there are two things worth nothing:
enumerating filenames with an unquoted string variable (for file in $files) is ill-advised, as sjsam's helpful answer points out: it will break with filenames with embedded spaces and filenames that look like globs; as stated, storing filenames in an array is the robust choice.
there is no strict need to change global shell option shopt -s extglob: parameter expansions can be nested, so the following would work without changing shell options:
# Sample values:
file='dir/sub-dir/joe'
set -- 'dir/' # set $1; value 'dir' would have the same effect.
filename=${file#${1%/}} # -> '/sub-dir/joe'
The inner parameter expansion, ${1%/}, removes a trailing (%) / from $1, if any.
I suggested you change files to an array which is a possible workaround for non-standard filenames that may contain spaces.
files=("dir/A/B" "dir/B" "dir/C")
for filename in "${files[#]}"
do
echo ${filename##dir/} #replace dir/ with your param.
done
Output
A/B
B
C
Here's the documentation from man bash under "Parameter Expansion":
${parameter#word}
${parameter##word}
Remove matching prefix pattern. The word is
expanded to produce a pattern just as in pathname
expansion. If the pattern matches the beginning of
the value of parameter, then the result of the
expansion is the expanded value of parameter with
the shortest matching pattern (the ``#'' case) or
the longest matching pattern (the ``##'' case)
deleted.
Since # tries to delete the shortest match, it will never include any trailing optional parts.
You can just use ## instead:
filename=${file##${1}?(/)}
Depending on what your script does and how it works, you can also just rewrite it to cd to the directory to always work with paths relative to .

How to break a string across lines in a makefile without spaces?

In a makefile, escaping a new-line with \ allows to split a single-line long string content across multiple source lines. However, the new-line is replaced with a space. Is there a transparent line break in the source that does not affect the string content?
VAR=w\
o\
r\
d
all:
echo $(VAR)
The desired output is 'word', but the actual output is 'w o r d'.
The simplest solution is to use $\<newline> to split the
line (at least if you are using GNU Make):
VAR = w$\
o$\
r$\
d
all:
echo $(VAR)
The output will be "word" with no spaces. This is because GNU
Make will replace backslash-newline-whitespace with a single
space, making the assignment to VAR be equivalent to:
VAR = w$ o$ r$ d
From
https://www.gnu.org/software/make/manual/html_node/Reference.html#Reference:
"A dollar sign followed by a character other than a dollar sign,
open-parenthesis or open-brace treats that single character as
the variable name." So the $<space> pairs are expansions of
the variable whose name is a single space character. Since this
variable is not defined by default, it will expand to the empty
string.
Note that the variable VAR will still contain the
$<space> pairs until it is expanded. Most of the time, this
doesn't matter, but if your makefile depends on using
$(value VAR) to process the underlying (unexpanded) value,
the above technique may provide surprising results.
Also, the recently released GNU Make 4.3 now explicitly documents this
technique for splitting lines (https://www.gnu.org/software/make/manual/make.html#Splitting-Lines):
Splitting Without Adding Whitespace
If you need to split a line but do not want any whitespace added, you can utilize a subtle trick: replace your backslash/newline pairs with the three characters dollar sign/backslash/newline:
var := one$\
word
After make removes the backslash/newline and condenses the following line into a single space, this is equivalent to:
var := one$ word
Then make will perform variable expansion. The variable reference ‘$ ’ refers to a variable with the one-character name “ ” (space) which does not exist, and so expands to the empty string, giving a final assignment which is the equivalent of:
var := oneword
For other ideas, see my answer to a similar question here:
How can I break a variable definition across multiple lines in a Makefile without spaces?
A longer treatment of line continuation options can be found in
my article "GNU Make line continuations":
http://drmikehenry.com/gnu-make-line-continuations/
This was just asked yesterday: How can I break a variable definition across multiple lines in a Makefile without spaces?
The short answer is no, there's no way to do that. This behavior is required by the POSIX standard for make.
All you can do is try postprocessing the string to remove the whitespaces using $(subst ...) or similar.

Bash bad substitution with subshell and substring

A contrived example... given
FOO="/foo/bar/baz"
this works (in bash)
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
this doesn't
BAZ=${$(basename $FOO):0:1} # result is bad substitution
My question is which rule causes this [subshell substitution] to evaluate incorrectly? And what is the correct way, if any, to do this in 1 hop?
First off, note that when you say this:
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
the first bit in the construct for BAZ is BAR and not the value that you want to take the first character of. So even if bash allowed variable names to contain arbitrary characters your result in the second expression wouldn't be what you want.
However, as to the rule that's preventing this, allow me to quote from the bash man page:
DEFINITIONS
The following definitions are used throughout the rest of this docu‐
ment.
blank A space or tab.
word A sequence of characters considered as a single unit by the
shell. Also known as a token.
name A word consisting only of alphanumeric characters and under‐
scores, and beginning with an alphabetic character or an under‐
score. Also referred to as an identifier.
Then a bit later:
PARAMETERS
A parameter is an entity that stores values. It can be a name, a num‐
ber, or one of the special characters listed below under Special Param‐
eters. A variable is a parameter denoted by a name. A variable has a
value and zero or more attributes. Attributes are assigned using the
declare builtin command (see declare below in SHELL BUILTIN COMMANDS).
And later when it defines the syntax you're asking about:
${parameter:offset:length}
Substring Expansion. Expands to up to length characters of
parameter starting at the character specified by offset.
So the rules as articulated in the manpage say that the ${foo:x:y} construct must have a parameter as the first part, and that a parameter can only be a name, a number, or one of the few special parameter characters. $(basename $FOO) is not one of the allowed possibilities for a parameter.
As for a way to do this in one assignment, use a pipe to other commands as mentioned in other responses.
Modified forms of parameter substitution such as ${parameter#word} can only modify a parameter, not an arbitrary word.
In this case, you might pipe the output of basename to a dd command, like
BAR=$(basename -- "$FOO" | dd bs=1 count=1 2>/dev/null)
(If you want a higher count, increase count and not bs, otherwise you may get fewer bytes than requested.)
In the general case, there is no way to do things like this in one assignment.
It fails because ${BAR:0:1} is a variable expansion. Bash expects to see a variable name after ${, not a value.
I'm not aware of a way to do it in a single expression.
As others have said, the first parameter of ${} needs to be a variable name. But you can use another subshell to approximate what you're trying to do.
Instead of:
BAZ=${$(basename $FOO):0:1} # result is bad substitution
Use:
BAZ=$(_TMP=$(basename $FOO); echo ${_TMP:0:1}) # this works
A contrived solution for your contrived example:
BAZ=$(expr $(basename $FOO) : '\(.\)')
as in
$ FOO=/abc/def/ghi/jkl
$ BAZ=$(expr $(basename $FOO) : '\(.\)')
$ echo $BAZ
j
${string:0:1},string must be a variable name
for example:
FOO="/foo/bar/baz"
baz="foo"
BAZ=eval echo '${'"$(basename $FOO)"':0:1}'
echo $BAZ
the result is 'f'

Resources