Process an external script using Makefile variables - makefile

I'm trying to include an external preprocess script to my current Makefile flow.
The external script location is /home/preprocessscript.sh, and content is:
cd ${path}; rm -rf *
Makefile content is:
path=/home/rubbish
clean:
cat ${preprocessscript} >> clean.sh
I execute the command by:
make clean preprocessscript=/home/preprocessscript.sh
./clean.sh
The problem is clean.sh doesn't get path. Its content will be:
cd ; rm -rf *
How do I pass the make variable to preprocessscript?

The problem is clean.sh doesn't get path. Its content will be:
cd ; rm -rf *
I'm sorry about the contents of your home directory.
Anyway, no. The cat command will output the contents of the original file verbatim. It will not interpret them in any way, and in particular, it will not attempt to expand shell-style variable references within.
Supposing that you have faithfully represented your situation, what you actually see is that when you run the resulting clean.sh as a script, the ${path} within expands to nothing. This is not particularly surprising, because nowhere in what you've presented is an environment variable named path defined. You have defined a make variable of that name, but that's not the same thing, and it anyway would not have an effect persisting past the end of the make run to when the generated script is executed.
I guess the idea is that you want to use the original file as a template to generate content for clean.sh. There are lots of ways to do that sort of thing, but the one closest to your current attempt would probably be to output (shell) variable assignments into the generated script, like so:
path = /home/rubbish
clean:
echo "path='$(path)'" >> clean.sh
cat ${preprocessscript} >> clean.sh
The generated script lines would then be
path='/home/rubbish'
cd ${path}; rm -rf *
Note also that there appear to be several other issues with the general approach presented in the question, but I am focusing my comments on the specific question asked.

You can export a make variable to the shell before running your command and use envsubst to expand the variables in the file as so:
path=/home/rubbish
clean:
export path=${path}; envsubst < ${preprocessscript} >> clean.sh
notice that the export has to be on the same recipe line as the command. You can also pipe the output of a command to envsubst: cat ${preprocessscript} | envsubst >> clean.sh. This might be useful if you're trying to do something more complicated like only print a few lines, etc.

Related

workaround to make exporting environment variable from makefile possible

i have a tricky conundrum for you all!
i would like to set environment variables with a makefile. i know, that the called process cannot change the calling environment's variables, but there has to be some workaround.
in the makefile:
target:
export VAR=test
later, in the terminal:
echo $VAR
should print "test".
how can this be achieved with some kind of workaround?
thank you very much for any help!
As Andreas says, there is no "workaround" for this. It's a fundamental feature of a POSIX operating system. The only way for the parent process (e.g. a shell) to have its environment modified is by doing something different than simply run make. If you're willing to do that, then you have options.
For example, if your makefile does this:
target:
#echo 'export VAR=test'
then in your shell you can do this:
$ eval $(make target)
and now that variable will be set. Of course, this will fail miserably if your makefile prints ANYTHING except valid shell syntax so you can only do a very limited set of things.
Alternatively you can have the makefile write stuff to a file then source the file, like this:
target:
echo 'export VAR=test' > target
then:
$ make target
$ . target
Make does updates to files, so what you need to do is "export" the variable to a file, and then later in terminal call something using the content of the file. Possible convoluted solution:
-- Makefile --
target:
echo "test" > VAR
-- Terminal --
cat VAR; # Prints var content, like echo does
cat VAR | xargs echo; # Sends each token (?) as argument to echo, printing it.
# Here be dragons.

Override command for make from outside

I have several dirs with files stamp1.txt and stamp0.txt, and i want override cat command. I need it for example to suppress 'stamp1' files from archiving into library.
So i wrote little filter program called 'realname' and bash script to override original cat command.
function cat() {
local e=""
for s in $#
do
if realname $s; then
e=$e" "$s;
fi
done
command cat $e;
}
So command:
cat dir1/stamp1.txt dir2/stamp0.txt
will be converted to
cat dir2/stamp0.txt
And this example works just fine
ar cruv some_lib.a `cat dir1/stamp1.txt dir2/stamp0.txt`
But when i run some makefile to build some software - inside this process used original cat nor overrided.
How to override cat or any other command in way to get it work for make process without changing makefile(makefile is 3rdparty software and i don't want patch it every time when upgrade is needed)?
You can't do that with a shell function, because a shell function exists only in the local shell. It's not passed to programs like make. Also, GNU make always invokes /bin/sh by default, not /bin/bash, and your shell function above is written in bash syntax, so putting it in your ~/.bashrc will have no impact.
You could run:
$ make SHELL=/bin/bash
and add that shell function to your ~/.bashrc and that might work.
The only other thing you can do (assuming that your third party makefile invokes cat directly and doesn't use a variable like $(CAT) instead) is to create a cat shell script (not a function) and put it on your PATH before /bin and /usr/bin when you invoke make. Something like:
$ mkdir tmp
$ vi tmp/cat
...add commands...
$ chmod 755 tmp/cat
$ PATH=$(pwd)/tmp:$PATH make ...
Of course when you do this you can't use command cat ... in your script, you'll have to use a fully-qualified path like /bin/cat ...
Short version: you cannot (and you probably should not want to).
Longer version: You've defined a shell function. You could actually even export that shell function into the environment export -f cat. But unless the Makefile was saying bash -c cat ... instead of just cat (or other reference to the same effect), it would not give you the behavior you wanted.
If you really insisted... and the Makefile did not use hard coded path (e.g. /bin/cat). You could write your own cat, place it somewhere and make this location precede other possible hits for cat (just put it up front).
There is also some chance (look in the make file) it uses a variable (e.g. CAT) to know what to call, so you could just provide your own definition if that was the case.
In any case though. I would discourage you from using workaround like these because the actual behavior of the machinery gets obfuscated by doing so. There is something declared here... and something else in the environment giving it a different meaning. Which is a very common source of mistakes and eventually non-obvious (harder to resolve) bugs.
Example/clarification to the function bit. I have a Makefile:
all:
#echo foo
And define and export a function "overriding" echo. echo() { /bin/echo "$#" bar; } ; export -f echo. I run make and get:
$ make
foo
Because make just looks for echo in PATH (tries to exec and once it finds it, it runs it). If I changed it to have bash step in between, the exported function would kick in, but... that's an usual way to use commands in make and you'd have to edit the Makefile which you did not want:
all:
#bash -c 'echo foo'
This would yield you the result you wanted:
$ make
foo bar
The other option I've mentioned. I've put behavior of that function into a script /tmp/bin/echo reading:
#!/bin/bash
/bin/echo "$#" bar
And I've modified the PATH env var export PATH=/tmp/bin:$PATH. Now even with the first form of the Makefile:
all:
#echo foo
I get:
$ make
foo bar
But if the Makefile that is given says /bin/echo instead, I'd have no such luck. You could still change the binary... or change its behavior by forcing a shared library preload... but sounds quote extreme and fully exposes why this really might not be the best direction to take it.

Deleting a directory contents using shell scripts

I am a newbie to Shell scripting. I want to delete all the contents of a directory which is in HOME directory of the user and deleting some files which are matching with my conditions. After googled for some time, i have created the following script.
#!/bin/bash
#!/sbin/fuser
PATH="$HOME/di"
echo "$PATH";
if [ -d $PATH ]
then
rm -r $PATH/*
fuser -kavf $PATH/.n*
rm -rf $PATH/.store
echo 'File deleted successfully :)'
fi
If I run the script, i am getting error as follows,
/users/dinesh/di
dinesh: line 11: rm: command not found
dinesh: line 12: fuser: command not found
dinesh: line 13: rm: command not found
File deleted successfully :)
Can anybody help me with this?
Thanks in advance.
You are modifying PATH variable, which is used by the OS defines the path to find the utilities (so that you can invoke it without having to type the full path to the binary). The system cannot find rm and fuser in the folders currently specified by PATH (since you overwritten it with the directory to be deleted), so it prints the error.
tl;dr DO NOT use PATH as your own variable name.
PATH is a special variable that controls where the system looks for command executables (like rm, fuser, etc). When you set it to /users/dinesh/di, it then looks there for all subsequent commands, and (of course) can't find them. Solution: use a different variable name. Actually, I'd recommend using lowercase variables in shell scripts -- there are a number of uppercase reserved variable names, and if you try to use any of them you're going to have trouble. Sticking to lowercase is an easy way to avoid this.
BTW, in general it's best to enclose variables in double-quotes whenever you use them, to avoid trouble with some parsing the shell does after replacing them. For example, use [ -d "$path" ] instead of [ -d $path ]. $path/* is a bit more complicated, since the * won't work inside quotes. Solution: rm -r "$path"/*.
Random other notes: the #!/sbin/fuser line isn't doing anything. Only the first line of the script can act as a shebang. Also, don't bother putting ; at the end of lines in shell scripts.
#!/bin/bash
path="$HOME/di"
echo "$path"
if [ -d "$path" ]
then
rm -r "$path"/*
fuser -kavf "$path"/.n*
rm -rf "$path/.store"
echo 'File deleted successfully :)'
fi
This line:
PATH="$HOME/di"
removes all the standard directories from your PATH (so commands such as rm that are normally found in /bin or /usr/bin are 'missing'). You should write:
PATH="$HOME/di:$PATH"
This keeps what was already in $PATH, but puts $HOME/di ahead of that. It means that if you have a custom command in that directory, it will be invoked instead of the standard one in /usr/bin or wherever.
If your intention is to remove the directory $HOME/di, then you should not be using $PATH as your variable. You could use $path; variable names are case sensitive. Or you could use $dir or any of a myriad other names. You do need to be aware of the key environment variables and avoid clobbering or misusing them. Of the key environment variables, $PATH is one of the most key ($HOME is another; actually, after those two, most of the rest are relatively less important). Conventionally, upper case names are reserved for environment variables; use lower case names for local variables in a script.

Directory based environment variable scope - how to implement?

I have a set of tools which I need to pass parameters depending on the project I'm working on. I'd like to be able to automatically set a couple of environment variables based on the current directory. So when I switched between directories, my commonly used env vars would also change. Example:
Let's current directory is foo, thus if I do:
~/foo$ ./myscript --var1=$VAR1
VAR1 would have some foo based value.
Then, let's say I switched to bar directory. If I do:
~/bar$ ./myscript --var1=$VAR1
VAR1 should now have some bar based value.
Is that possible? How?
the ondir program lets you specify actions to run when you enter and leave directories in a terminal
There is direnv which helps you do this stuff much easily and in an elegant way. Just define a .envrc file in your project directory with all the env variables needed and it will source it once you cd into that folder.
I've written another implementation of this, which is somewhat similar to ondir. I didn't actually know about ondir when I started working on it. There are some key differences that may be useful, however.
smartcd is written entirely in shell, and is fully compatible with bash and zsh, even the more esoteric options
smartcd will run scripts all the way down and up the directory hierarchy down to their common ancestor, not just for the two directories you're entering and leaving. This means you can have a ~/foo script that will execute whether you "cd ~/foo" or "cd ~/foo/bar"
it has "variable stashing" which is a more automatic way of dealing with your environment variables, whereas ondir requires you to explicitly and manually remove and/or reset your variables
smartcd can work with "autocd" turned on by hooking your prompt command (PROMPT_COMMAND in bash, precmd in zsh)
You can find smartcd at https://github.com/cxreg/smartcd
This is not something that is directly supported with the built-in features of bash or any other common shell. However, you can create your own "cd" command that will do whatever you want. For example, you could alias cd to do the cd and then run a special script (eg: ~/bin/oncd). That script could look up the new directory in a database and run some commands, or see if there's a special file (eg: .env) in the directory and load it, etc.
I do this sort of thing a lot. I create several identically named batch files in directories where I need them that only set the variables and call the common script. I even have a batch file that creates the other small files.
This is not pretty, but you can use a combination of exported environment variables and the value of $PWD.
For example:
export VAR1=prefix
export prefix${HOME////_}_foo=42
export prefix${HOME////_}_bar=blah
Then myscript needs only to eval echo \${$VAR1${PWD////_}} to get at the directory based value.
How about wrap your script with a function (the function can be placed either in your bash profile/bashrc file in the system ones to make available for all the users ).
myscript () { case $PWD in
/path/to/foo) path/to/myscript --var1=$VAR1 ;;
/path/to/bar) path/to/myscript --var2=$VAR1 ;;
*) ;;
case
}
Hence the function myscript will call the real "myscript" knowing what to do based on the current working directory.
Take this as an example:
hmontoliu#ulises:/tmp$ myscript () { case $PWD in /tmp) echo I\'m in tmp;; /var) echo I\'m in var;; *) echo I\'m neither in tmp nor in bar; esac; }
hmontoliu#ulises:/tmp$ myscript
I'm in tmp
hmontoliu#ulises:/tmp$ cd /var
hmontoliu#ulises:/var$ myscript
I'm in var
hmontoliu#ulises:/var$ cd /etc
hmontoliu#ulises:/etc$ myscript
I'm neither in tmp nor in bar

How to run my own programm using command in Shell?

I just learned that I could use chmod make myscript.sh executable and the run it as $ ./myscript.sh But how can I attach a custom command to it, like $ connectme [options] ?
You need to do two things:
Give the name you want to use. Either just rename it, or establish a link (hard or symbolic). Make sure the correctly named object has the right permissions.
Make sure it is in you path. But putting "." in you PATH is a bad idea (tm), so copy it to $HOME/bin, and put that in you path.
A completely different approach. Most shells support aliases. You could define one to run your script.
Note: The environment variable PATH tells the shell where to look for programs to run (unless you specify a fully qualified path like /home/jdoe/scripts/myscript.sh or ./myscript.sh), it consists of a ":" seperated list of directories to examine. You can check yours with:
$ printenv PATH
resulting for me in
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11/bin:/usr/X11R6/bin
which are the usual directories for binaries. You can add a new path element with (in /bin/sh and derivatives):
$ export PATH=$PATH:$HOME/bin
in csh and derivatives use
$ setenv PATH $PATH:$HOME/bin
either of which which will result in the shell also searching ~/bin for things to run. Then move your script into that directory (giving ta new name if you want). Check that you execute permissions for the script, and just type its name like any other command.
Fianlly, the use of a ".sh" extension to denote a shell script is for human consumption only. Unix does not care about how you name your script: it is the so-called "shebang" ("#!") on the first line of the script that the OS uses to find the interpreter.
You need to learn about arguments in BASH PROGRAMMING. Here is a good tutorial on them. Check section #4 out.
Basically, you need to use special variables $1, $2, $3 to refer to first, second and third command line arguments respectively.
Example:
$ ./mycript.sh A-Rod
With myscript.sh being:
#!/bin/bash
echo "Hello $1"
Will print:
Hello A-Rod

Resources