Meaning of $(VAR): in makefile - linux

I found in a makefile the following commands:
$(var):
mkdir -p $(#D)
What is the meaning of this command?

$(VAR) expands to the value of the variable VAR. This is a Make variable (not a shell etc variable). For example, if earlier in your Makefile you define
VAR=ick/poo
then VAR expands to ick/poo, and #D in your recipe expands to the directory part, ick.
As you seem to be confused about the relationship between shell and make, I should perhaps point out that these are two different languages, though in a Makefile, you will encounter both; the recipes - the parts which are indented by a tab - will be passed to a shell for evaluation (though normally the shell will be /bin/sh, not Bash, unless you specifically override the Make variable SHELL to force it).
In the shell, by the way, the superficially similar construct $(cmd) performs a command substitution; that is, the command cmd will be evaluated and its output will be inserted as text. So for example,
echo Running in $(pwd)
will print
Running in /home/you
if executed in the directory /home/you (the command pwd prints out your current working directory). ... Though in a Makefile, the dollar sign will normally be evaluated and consumed by make itself; so to pass a literal dollar sign to the shell, you have to double it.
test:
echo Running in $$(pwd)

As already explained by #tripleee $(var) expands to the variable. Because it is here listed before a colon it means that it is a target in a Makefile.
For $(#D) see 10.5.3 Automatic Variables in the make manual:
The directory part of the file name of the target, with the trailing slash removed. If the value of ‘$#’ is dir/foo.o then ‘$(#D)’ is dir. This value is . if ‘$#’ does not contain a slash.
NOTE: This is NOT a shell script. This is a makefile. Please use "man make" for a description about what "make" does.

Related

Using 'rm -rf' in Linux PowerShell and Linux bash

I need to execute "rm -rf" on remote machine running Ubuntu in order to clear specified folder.
If I use command like following everything goes fine.
rm -rf "home/blahblah/blah"/*
But if I run the same command in Linux PowerShell I would get ALL files removed.
Is there any way to specify path to be handled the same way in bash and PS? Thank you!
tl;dr
Unfortunately, as of PowerShell 7.0, you must do the following (if you want to use the external rm utility):
sh -c "rm -rf 'home/blahblah/blah'/*"
Note that I've switched to single-quoting ('...') around the path, so I could use it inside a double-quoted ("...") string. While the reverse ('... "..."/*') should work as-is, it currently requires additional escaping ('... \"...\"/*') - see this answer.
However, if the path preceding the /* doesn't actually need quoting - notably if it doesn't contain spaces - you can simply call:
rm -rf home/blahblah/blah/*
You're seeing a very unfortunate difference between PowerShell (as of 7.0) and POSIX-like shells such as Bash with respect to the handling of string arguments composed of both quoted and unquoted parts.
PowerShell parses "home/blahblah/blah"/* as two arguments:
home/blahblah/blah becomes the first argument, as-is in this case.
/* is interpreted as the second argument, which, due to being unquoted and due to an external utility being called, triggers PowerShell's emulation of the globbing (filename expansion) behavior of POSIX-like shells, which means that all files and directories in the root directory are being passed as individual arguments.
Therefore, the arguments that the rm utility actually receives are: -rf, home/blahblah/blah, /bin, /boot, /dev, ... - which is clearly not the intent.
This problematic behavior is discussed in this GitHub issue.
Passing the command to sh, the default shell on Unix-like platforms, instead, bypasses the problem.

Escaping a system variable to pass as an argument to make

I have a makefile generated by bakefile which is working fine. To run the executable it needs libraries from a different folder. I found the command rpath used to specify the path to these library. But I have to send it to the makefile as an argument when using the command.
I cannot specify it directly from the bakefile.
I can use the LDFLAGS arguments which is fine. And I found here how to use the $ORIGIN variable.
My question is how does this escaping works?
make LDFLAGS="-Wl,-rpath '-Wl,\$\$ORIGIN'"
Is the single quote to prevent make to interpret the variable?
And why the \$ is here twice?
Yeesh. What a mess.
So, the first set of quotes is removed by the shell, before it starts the make command. Since the outer set of quotes is double-quotes, you have to escape the $ otherwise the shell will treat it as a shell variable (compare to a command like echo "my path is $PWD" and how the PWD variable is expanded). The shell uses backslashes to quote things like $.
So, by the time the shell hands the command line to make, it sees the setting LDFLAGS=-Wl,-rpath '-Wl,$$ORIGIN'
Next in your makefile will be a recipe with a command like this:
$(LD) $(LDFLAGS) ...
Make will expand the LDFLAGS variable as above. For make, any name preceded by a $ is considered a make variable and you escape it from expansion by make by doubling the $ (not using backslashes like the shell), and writing $$. Make will remove one $ during expansion.
So, make will reduce LDFLAGS to the string -Wl,-rpath '-Wl,$ORIGIN' and pass that to the shell.
The shell will strip the next level of quoting, which in this case is the single quotes. Variables are not expanded inside single quotes, so the linker actually gets arguments, literally, -Wl,-rpath and -Wl,$ORIGIN, which is what you want.

File execution with dot space versus dot slash

I am attempting to work with an existing library of code but have encountered an issue. In short, I execute a shell script (let's call this one A) whose first act is to call another script (B). Script B is in my current directory (a requirement of the program I'm using). The software's manual makes reference to bash, however comments in A suggest it was developed in ksh. I've been operating in bash so far.
Inside A, the line to execute B is simply:
. B
It uses the "dot space" syntax to call the program. It doesn't do anything unusual like sudo.
When I call A without dot space syntax, i.e.:
./A
it always errors saying it cannot find the file B. I added pwd, ls, whoami, echo $SHELL, and echo $PATH lines to A to debug and confirmed that B is in fact right there, the script is running with the same $SHELL as I am at the command prompt, the script is the same user as I am, and the script has the same search path $PATH as I do. I also verified if I do:
. B
at the command line, it works just fine. But, if I change the syntax inside A to:
./B
instead, then A executes successfully.
Similarly, if I execute A with dot space syntax, then both . B and ./B work.
Summarizing:
./A only works if A contains ./B syntax.
. A works for A with either ./B or . B syntax.
I understand that using dot space (i.e. . A) syntax executes without forking to a subshell, but I don't see how this could result in the behavior I'm observing given that the file is clearly right there. Is there something I'm missing about the nuances of syntax or parent/child process workspaces? Magic?
UPDATE1: Added info indicating that the script may have been developed in ksh, while I'm using bash.
UPDATE2: Added checking to verify $PATH is the same.
UPDATE3: The script says it was written for ksh, but it is running in bash. In response to Kenster's answer, I found that running bash -posix then . B fails at the command line. That indicates that the difference in environments between the command line and the script is that the latter is running bash in a POSIX-compliant mode, whereas the command line is not. Looking a little closer, I see this in the bash man page:
When invoked as sh, bash enters posix mode after the startup files are read.
The shebang for A is indeed #!/bin/sh.
In summary, when I run A without dot space syntax, it's forking to its own subshell, which is in POSIX-compliant mode because the shebang is #!/bin/sh (instead of, e.g., #!/bin/bash. This is the critical difference between the command line and script runtime environments that leads to A being unable to find B.
Let's start with how the command path works and when it's used. When you run a command like:
ls /tmp
The ls here doesn't contain a / character, so the shell searches the directories in your command path (the value of the PATH environment variable) for a file named ls. If it finds one, it executes that file. In the case of ls, it's usually in /bin or /usr/bin, and both of those directories are typically in your path.
When you issue a command with a / in the command word:
/bin/ls /tmp
The shell doesn't search the command path. It looks specifically for the file /bin/ls and executes that.
Running ./A is an example of running a command with a / in its name. The shell doesn't search the command path; it looks specifically for the file named ./A and executes that. "." is shorthand for your current working directory, so ./A refers to a file that ought to be in your current working directory. If the file exists, it's run like any other command. For example:
cd /bin
./ls
would work to run /bin/ls.
Running . A is an example of sourcing a file. The file being sourced must be a text file containing shell commands. It is executed by the current shell, without starting a new process. The file to be sourced is found in the same way that commands are found. If the name of the file contains a /, then the shell reads the specific file that you named. If the name of the file doesn't contain a /, then the shell looks for it in the command path.
. A # Looks for A using the command path, so might source /bin/A for example
. ./A # Specifically sources ./A
So, your script tries to execute . B and fails claiming that B doesn't exist, even though there's a file named B right there in your current directory. As discussed above, the shell would have searched your command path for B because B didn't contain any / characters. When searching for a command, the shell doesn't automatically search the current directory. It only searches the current directory if that directory is part of the command path.
In short, . B is probably failing because you don't have "." (current directory) in your command path, and the script which is trying to source B is assuming that "." is part of your path. In my opinion, this is a bug in the script. Lots of people run without "." in their path, and the script shouldn't depend on that.
Edit:
You say the script uses ksh, while you are using bash. Ksh follows the POSIX standard--actually, KSH was the basis for the POSIX standard--and always searches the command path as I described. Bash has a flag called "POSIX mode" which controls how strictly it follows the POSIX standard. When not in POSIX mode--which is how people generally use it--bash will check the current directory for the file to be sourced if it doesn't find the file in the command path.
If you were to run bash -posix and run . B within that bash instance, you should find that it won't work.

What does this shell script line of code mean

I need some help understanding following shell script line,
apphome = "`cd \`dirname $0\` && pwd && cd - >/dev/null`"
All I understand is, this is creating a variable called apphome.
This is not a valid shell code.
The shell don't allow spaces around =
For the rest, while this seems broken, it try to cd to the dir of the script itself, display the current dir & finally cd back to the latest cd place redirecting his standard output STDOUT to the /dev/null trash-bin (that's makes not any sense, cd display only on standard error STDERR when it fails, never on STDOUT)
If you want to do this in a proper a simple way :
apphome="$(dirname $0)"
That's all you need.
NOTE
The backquote
`
is used in the old-style command substitution, e.g.
foo=`command`
The
foo=$(command)
syntax is recommended instead. Backslash handling inside $() is less surprising, and $() is easier to nest. See http://mywiki.wooledge.org/BashFAQ/082
It seems to assign a command to the "apphome" variable. This command can be executed later.
dirname returns a directory portion of a file name. $0 is the name of the script this line contains (if I am not mistaken).
Now, executing dirname <name> will return a directory, and cd will use the value.
So, what it would do is execute three command in the row assuming that each one of them succeeds. The commands are:
cd `dirname [name of the script]`
pwd
cd -
First command will change directory to the directory containing your script; second will print current directory; third will take yo back to the original directory. Output of the third command will not be printed out.
In summary, it will print out a name of a directory containing the script that contains the line in question.
At least, this is how I understand it.

How do I access an environment variable in a .desktop file's exec line?

My program requires an environment variable as part of one of its parameters:
myprogram --folder=$HOME/.special
However, if I put this into a .desktop file's exec line, it doesn't work:
Exec=myprogram --folder=$HOME/.special
The $HOME seems to resolve to nothing.
By default environment variables do not seem to be resolved by all implementations, however you can instead exec sh, which will resolve the passed environment variable. Note that the desktop spec also requires you to escape the = and $ character with a backslash. So you want:
Exec=sh -c "myprogram --folder\=\$HOME/.special"
For the full list of characters that need escaping, see the specification

Resources