sed and capturing groups in Makefile [duplicate] - linux

When inserting a shell script inside a Makefile we have (?) to use a double dollar sign ($$) to make reference to variables. Why is that so?
for number in 1 2 3 4 ; do \
echo $$number ; \
done

As per gnu make official doc:
Variable and function references in recipes have identical syntax and
semantics to references elsewhere in the makefile. They also have the
same quoting rules: if you want a dollar sign to appear in your
recipe, you must double it (‘$$’). For shells like the default shell,
that use dollar signs to introduce variables, it’s important to keep
clear in your mind whether the variable you want to reference is a
make variable (use a single dollar sign) or a shell variable (use two
dollar signs).
So in short:
makefile variable => use a single dollar sign
shell variable => use two dollar signs

$$ is the PID(process ID) of your current process.
$ ps -ef|grep $$
user 208465 200620 0 10:30 pts/4 00:00:00 bash
$ for number in 1 2 3 4 ; do \
echo $$number ; \
done
208465number
208465number
208465number
208465number

Not directly applicable to this example -- except if the code shown is executed via $(shell ...) instead of being a rule:
With secondary expansion enabled, make might also interpret the double dollar itself in the second processing phase, when it occurrs in the prequisites list. (First phase: read file, set variables; second phase: find and invoke dependency targets, execute rules)
This is used to allow dynamically specifying dependency targets, when the variable with the targets name is only available later in the file.
See https://www.gnu.org/software/make/manual/html_node/Secondary-Expansion.html.

Related

How to create an associate array with value of a list of items in bash

I would like to create such a associated array in bash:
myarr = {
'key1' : ["command_name", "command name with arguments"],
'key2' : ["command_name", "command name with arguments"],
}
The reason I want to do the above is so that I can pass a key to the script and then do something like this:
Use the key to index into the associative array
Use some tool to check whether the application given by command_name is open
If the window is not open, lunch the application given by command name with arguments
Such a task is trivial in a popular programming language, but it doesn't seem to be as trivial in bash.
EDIT
I'd like to be able to create something like this:
declare -A array=(
[c]=("code" "code")
[e]=("dolphin" "XDG_CURRENT_DESKTOP=KDE dolphin")
[n]=("nvim" "kitty nvim")
)
Edit: take comments into account and replace now useless arrays by scalar strings.
As you want to set bash variables in the command's context we cannot execute them with "$cmd", this would not work for variable assignments. The following uses eval, which is extremely risky, especially if you do not fully control the inputs. A better solution, but more complicated, would be to use other variables for the execution environment, declare functions to limit the scope of variables and/or restore them afterwards, use eval only in last resort and only after sanitizing its parameters (printf '%q')... Anyway, you have been warned.
Storing bash commands and their arguments in variables is not recommended. But if you really need this it would be better to store the command names and the full commands in 2 different variables. They could be associative arrays or, if your bash is recent enough and supports namerefs, scalar variables named from your keys (if they are valid bash variable names).
Example where the key is stored in bash variable k, and the command is the second of your own example, plus some dummy arguments:
k="e"
# add a new command with key "$k"
declare -n cmd="${k}_cmd" lcmd="${k}_lcmd"
cmd="dolphin"
lcmd="XDG_CURRENT_DESKTOP=KDE dolphin arg1 arg2 'arg 3'"
...
# launch command with key "$k"
declare -n cmd="${k}_cmd" lcmd="${k}_lcmd"
if not_running "$cmd"; then
eval "$lcmd"
fi
Demo with key foo, command printf and arguments '%s\n' 'a b' 'c d':
$ k="foo"
$ declare -n cmd="${k}_cmd" lcmd="${k}_lcmd"
$ cmd="printf"
$ lcmd="printf '%s\n' 'a b' 'c d'"
$ eval "$lcmd"
a b
c d

Bash don't replace $ [duplicate]

This question already has answers here:
Difference between single and double quotes in Bash
(7 answers)
Closed 1 year ago.
I want to write a bash script which creates other bash scripts. But when I do
echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/SOME/PATH" >> NEWFILE.sh
it already replaces $LD_LIBRARY_PATH in the first script.
So in NEWFILE.sh I only get:
export LD_LIBRARY_PATH=:~/SOME/PATH
But i want that in the NEWFILE.sh there's still:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/SOME/PATH
So it gets replaced, when running NEWFILE.sh. I hope this makes sense. Thank you for your help
If you don't want the variable interpolated, use single quotes:
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/SOME/PATH' >> NEWFILE.sh
But even this small snippet has issues. When NEWFILE.sh runs with an empty LD_LIBRARY_PATH, it will create an invalid value that has a leading :. Also, it is bad practice to use ~ in variables. Instead, you should do:
echo 'export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}$HOME/SOME/PATH' >> NEWFILE.sh
Also, this snippet makes NEWFILE.sh not idempotent, and you may wind up with multiple instances of $HOME/SOME/PATH in the final value. This is easy enough to avoid, but takes a bit more logic. Something like:
cat << \EOF >> NEWFILE.sh
pathmunge () { case ":${LD_LIBRARY_PATH}:" in *:"$1":*) ;;
*) LD_LIBRARY_PATH="$LD_LIBRARY_PATH${LD_LIBRARY_PATH:+:}$1";; esac; }
pathmunge $HOME/SOME/PATH
export LD_LIBRARY_PATH
EOF
This uses a common technique of quoting the HEREDOC (the backslash before the EOF is essential; see how it changes when you remove the leading backslash) to prevent interpolation, and allows multi-line output and quotes to be used in the content that is going to be written to NEWFILE.sh.
One small point is that this will wind up putting the expansion of $HOME in LD_LIBRARY_PATH, which is probably what you want. If you really do want $HOME in the path (so that it is expanded when LD_LIBRARY_PATH is used, rather than when it is defined), you could do pathmunge '$HOME/path', but you may wind up with duplicate instances in the final value, since pathmunge will not recognize the unexpanded value as matching the expanded value. Avoiding that duplication is an exercise left for the reader.
Note that, depending on the remaining content of NEWFILE.sh, you may want to ensure that pathmunge is only defined once, and that this definition is not overriding some other definition. pathmunge is a common name for a function used for modifying PATH so you may want to consider a different name, or adding logic to allow it to take the name of the variable to be overridden.
You should escape the $ as follow:
echo "export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:~/SOME/PATH" >> NEWFILE.sh

How to get the complete calling command of a BASH script from inside the script (not just the arguments)

I have a BASH script that has a long set of arguments and two ways of calling it:
my_script --option1 value --option2 value ... etc
or
my_script val1 val2 val3 ..... valn
This script in turn compiles and runs a large FORTRAN code suite that eventually produces a netcdf file as output. I already have all the metadata in the netcdf output global attributes, but it would be really nice to also include the full run command one used to create that experiment. Thus another user who receives the netcdf file could simply reenter the run command to rerun the experiment, without having to piece together all the options.
So that is a long way of saying, in my BASH script, how do I get the last command entered from the parent shell and put it in a variable? i.e. the script is asking "how was I called?"
I could try to piece it together from the option list, but the very long option list and two interface methods would make this long and arduous, and I am sure there is a simple way.
I found this helpful page:
BASH: echoing the last command run
but this only seems to work to get the last command executed within the script itself. The asker also refers to use of history, but the answers seem to imply that the history will only contain the command after the programme has completed.
Many thanks if any of you have any idea.
You can try the following:
myInvocation="$(printf %q "$BASH_SOURCE")$((($#)) && printf ' %q' "$#")"
$BASH_SOURCE refers to the running script (as invoked), and $# is the array of arguments; (($#)) && ensures that the following printf command is only executed if at least 1 argument was passed; printf %q is explained below.
While this won't always be a verbatim copy of your command line, it'll be equivalent - the string you get is reusable as a shell command.
chepner points out in a comment that this approach will only capture what the original arguments were ultimately expanded to:
For instance, if the original command was my_script $USER "$(date +%s)", $myInvocation will not reflect these arguments as-is, but will rather contain what the shell expanded them to; e.g., my_script jdoe 1460644812
chepner also points that out that getting the actual raw command line as received by the parent process will be (next to) impossible. Do tell me if you know of a way.
However, if you're prepared to ask users to do extra work when invoking your script or you can get them to invoke your script through an alias you define - which is obviously tricky - there is a solution; see bottom.
Note that use of printf %q is crucial to preserving the boundaries between arguments - if your original arguments had embedded spaces, something like $0 $* would result in a different command.
printf %q also protects against other shell metacharacters (e.g., |) embedded in arguments.
printf %q quotes the given argument for reuse as a single argument in a shell command, applying the necessary quoting; e.g.:
$ printf %q 'a |b'
a\ \|b
a\ \|b is equivalent to single-quoted string 'a |b' from the shell's perspective, but this example shows how the resulting representation is not necessarily the same as the input representation.
Incidentally, ksh and zsh also support printf %q, and ksh actually outputs 'a |b' in this case.
If you're prepared to modify how your script is invoked, you can pass $BASH_COMMANDas an extra argument: $BASH_COMMAND contains the raw[1]
command line of the currently executing command.
For simplicity of processing inside the script, pass it as the first argument (note that the double quotes are required to preserve the value as a single argument):
my_script "$BASH_COMMAND" --option1 value --option2
Inside your script:
# The *first* argument is what "$BASH_COMMAND" expanded to,
# i.e., the entire (alias-expanded) command line.
myInvocation=$1 # Save the command line in a variable...
shift # ... and remove it from "$#".
# Now process "$#", as you normally would.
Unfortunately, there are only two options when it comes to ensuring that your script is invoked this way, and they're both suboptimal:
The end user has to invoke the script this way - which is obviously tricky and fragile (you could however, check in your script whether the first argument contains the script name and error out, if not).
Alternatively, provide an alias that wraps the passing of $BASH_COMMAND as follows:
alias my_script='/path/to/my_script "$BASH_COMMAND"'
The tricky part is that this alias must be defined in all end users' shell initialization files to ensure that it's available.
Also, inside your script, you'd have to do extra work to re-transform the alias-expanded version of the command line into its aliased form:
# The *first* argument is what "$BASH_COMMAND" expanded to,
# i.e., the entire (alias-expanded) command line.
# Here we also re-transform the alias-expanded command line to
# its original aliased form, by replacing everything up to and including
# "$BASH_COMMMAND" with the alias name.
myInvocation=$(sed 's/^.* "\$BASH_COMMAND"/my_script/' <<<"$1")
shift # Remove the first argument from "$#".
# Now process "$#", as you normally would.
Sadly, wrapping the invocation via a script or function is not an option, because the $BASH_COMMAND truly only ever reports the current command's command line, which in the case of a script or function wrapper would be the line inside that wrapper.
[1] The only thing that gets expanded are aliases, so if you invoked your script via an alias, you'll still see the underlying script in $BASH_COMMAND, but that's generally desirable, given that aliases are user-specific.
All other arguments and even input/output redirections, including process substitutiions <(...) are reflected as-is.
"$0" contains the script's name, "$#" contains the parameters.
Do you mean something like echo $0 $*?

Why $$PWD instead of $PWD in TI x-loader makefile for omap devices

I started to analyse the TI X-Loader, including it's makefiles. I found the following lines in the top makefile:
TOPDIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi)
and
ifeq (include/config.mk,$(wildcard include/config.mk))
...
Regarding the first line: i know that "$PWD" means the value of PWD (the working directory)
My question:
why the double dollar sign in this special case?
Regarding the second line: I'm trying to understand what is compared and why.
I already red the GNU MAKE manual wildcard explanation. I'm still missing something.
My questions:
what is the "$(wildcard" for?
Which config.mk files exactly are compared to each other?
Why are they compared? (this question is more about the makefile structure)
Thanks for your help in advance.
Martin
In make, you escape a dollar sign (so that it's not expanded by make) using two dollar signs ($$). So, if you write $PWD then make will interpret the $P as a make variable reference, which is likely not set and so empty, and the result (passed to the shell) will be WD. If you use $$PWD then the dollar sign is escaped and the result passed to the shell command will be $PWD which is what you want.
For the second line, the wildcard function will, among other things, expand to the empty string if there are no files that match the wildcard. So this invocation of wildcard is testing to see if the file include/config.mk exists or not. If it does, then the result of the function will be include/config.mk and the ifeq test will be true. If it doesn't then the result will be the empty string and the ifeq test will be false.
Nothing here is comparing the contents or timestamps or anything else of include/config.mk. This is purely a "does this file exist?" test.

Can colon be used as identifier?

I saw a code in The Bash command :(){ :|:& };: will spawn processes to kernel death. Can you explain the syntax? as follows
user#host$ :(){ :|:& };:
Here colon used as identifier for function name.
Can colon be used as identifier?
Yes, it can.
$ :()
> {
> echo "hello from : :)"
> }
$ :
hello from : :)
According to the documentation:
name
A word consisting solely of letters, numbers, and underscores, and beginning with a letter or underscore. Names are used as shell variable and function names. Also referred to as an identifier.
No, the colon is not valid in function names. So either the bomb doesn't work in bash, or the documentation is failing.
I shortly thought that the colon might refer to the built-in operator, but I don't see how that could get the expected semantics.
The implementation seems to be inconsistent. You can define and call a function containing colons and Google even suggests this for packages in their style guide.
Though I noticed you can not export nor unset these functions.
#!/usr/bin/env bash
foo:bar() {
echo "foo:bar called"
}
foo:bar
export -f foo:bar
unset foo:bar
The export won't complain but if you call another bash script afterwards foo:bar is not available.
The unset will even trigger an error:
/foo/bar: line 11: unset: `foo:bar': not a valid identifier
$ bash --version
GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu)

Resources