Makefile - "$" not taken into account for shell command - bash

I have a Makefle with the following rule
bash -c "find . |grep -E '\.c$|\.h$|\.cpp$|\.hpp$|Makefile' | xargs cat | wc -l"
I'm expecting make to run the quoted bash script and to return the number of line in my project.
Running directly the command in a terminal does the work, but it doesn't work in makefile.
If I remove $ from the script, it does work ... but not as expected (since I only want *.{c,cpp,h,hpp,Makefile}.
Why bash -c doesn't run correctly my script?

if you write the rule like the following, it should produce the result you want:
target:
#echo $(shell find . | grep -E '\.c$$|\.h$$|\.cpp$$|\.hpp$$|Makefile' | xargs cat | wc -l)

In Makefiles, $ is used for make variables such as $(HEADERS), where HEADERS would have been defined previously using =.
To use a $ in inline bash, you have to double them to escape them. $$VAR will refer to a shell variable, and .c$$ and so on should escape the $ for the regex you're working with.
The following should suffice in escaping the $'s for what you're trying to accomplish
bash -c "find . |grep -E '\.c$$|\.h$$|\.cpp$$|\.hpp$$|Makefile' | xargs cat | wc -l"
Additionally, you can use bash globally in your Makefile as opposed to the default /bin/sh if you add this declaration:
SHELL = /bin/bash
With the above, you should be able to use the find command without needing the bash -c and quotes. The following should work if SHELL is defined as above:
find . |grep -E '\.c$$|\.h$$|\.cpp$$|\.hpp$$|Makefile' | xargs cat | wc -l
Also, note that you can, and will often see SubShells used for this purpose. These are created with (). This will make any variables defined by the inner shell local to that shell and its group of commands.

Related

How to find the number of instances of current script running in bash?

I have the below code to find out the number of instances of current script running that is running with same arg1. But looks like the script creates a subshell and executes this command which also shows up in output. What would be the better approach to find the number of instances of running script ?
$cat test.sh
#!/bin/bash
num_inst=`ps -ef | grep $0 | grep $1 | wc -l`
echo $num_inst
$ps aux | grep test.sh | grep arg1 | grep -v grep | wc -l
0
$./test.sh arg1 arg2
3
$
I am looking for a solution that matches all running instance of ./test.sh arg1 arg2 not the one with ./test.sh arg10 arg20
The reason this creates a subshell is that there's a pipeline inside the command substitution. If you run ps -ef alone in a command substitution, and then separately process the output from that, you can avoid this problem:
#!/bin/bash
all_processes=$(ps -ef)
num_inst=$(echo "$all_processes" | grep "$0" | grep -c "$1")
echo "$num_inst"
I also did a bit of cleanup on the script: double-quote all variable references to avoid weird parsing, used $() instead of backticks, and replaced grep ... | wc -l with grep -c.
You might also replace the echo "$all_processes" | ... with ... <<<"$all_processes" and maybe the two greps with a single grep -c "$0 $1":
...
num_inst=$(grep -c "$0 $1" <<<"$all_processes")
...
Modify your script like this:
#!/bin/bash
ps -ef | grep $0 | wc -l
No need to store the value in a variable, the result is printed to standard out anyway.
Now why do you get 3?
When you run a command within back ticks (fyi you should use syntax num_inst=$( COMMAND ) and not back ticks), it creates a new sub-shell to run COMMAND, then assigns the stdout text to the variable. So if you remove the use of $(), you will get your expected value of 2.
To convince yourself of that, remove the | wc -l, you will see that num_inst has 3 processes, not 2. The third one exists only for the execution of COMMAND.

What is wrong this simple history script?

I am missing something really simple I think:
$ cat hs.sh
#!/bin/bash
echo $1
history | grep -i $1
echo $#
exit
$
here is output:
$ ./history_search sed
sed
1
$
Trying to create a script which I can use in form of './hs.sh sed' to search for all sed commands in history. I can create an alias using this which works fine, but not this script.
Here is the alias:
alias hg='history | grep -i $1'
Interactive shells have history; scripted shells do not have history. You can only ask for history from an interactive shell, which is why the alias works but the script does not.
When you run this as a shell script, it spawns a new shell that has no history.
Try running it in the same shell like this:
source ./history_search see
and it should work.

How can I tell what shell is running my init file?

I have an init file (/etc/profile.d/which2.sh) that aliases the which command whenever any shell starts. In bash or sh that is fine but in zsh I don't want that as which is a built-in that is already aware of aliases and functions. How can I have the script 'know' when it is under zsh and not execute the alias?
$0 does not help.
I have fixed the problem by simply unsetting the alias in zsh-specific ~/.zshrc, but I would like to know another way.
How about
[ "$(which which)" = /usr/bin/which ] && alias which "whichever"
This doesn't verify the name of the shell; rather it verifies the shell's behaviour. That's an instance of a generally-applicable programming paradigm: test behaviour directly whenever possible. (See, for example, browser detection.)
In this case, if you just checked the shell's name as a proxy for a behaviour check, you might luck out now, but things could break in the future. The name is actually arbitrary, and new names might easily be introduced. For example, in some distros ksh is a hard-link to zsh; zsh will adapt its behaviour in an attempt to emulate ksh. As another example, I have two different zsh versions, one of which is invoked as zsh5.
Ideally, the test wouldn't depend on the precise location of the which utility, either.
What are you testing for? In the old days when we had to determine whether we were running under Kornshell or Bournshell, we could do the following test:
if [ "$RANDOM" = "$RANDOM" ]
then
echo "This is the Bourne shell"
/bin/ksh $* # Script needs the Kornshell
else
echo "This is the Kornshell"
fi
Of course, both Kornshell and Bash expand $RANDOM (and so does zsh)...
Okay... You can find the processes running on the current tty this way:
ps -ft $(tty)
A little formatting:
$ ps -ocommand="" -t$(tty)
login -pf david
-ksh
bash
Pretty good. Shells end with sh, so I'll make that assumption. I just want the lines ending with sh:
$ ps -ocommand="" -t$(tty) | grep "sh$"
-ksh
bash
Yes, I'm running two shells in this TTY. I login with the Kornshell, and shelled out to bash. Let's toss the PID in the mix:
$ ps -t$(tty) -opid="" -ocommand="" | grep "sh$"
62599 -ksh
62855 bash
We want the one with the highest PID
$ ps -t$(tty) -opid="" -ocommand="" | grep "sh$" | sort -k1,1nr | head -1
62983 bash
Yup, I'm running Bash. Let's get rid of the PID:
$ ps -t$(tty) -opid="" -ocommand="" | grep "sh$" | sort -k1,1nr | head -1 | sed 's/.* //'
bash
Let's see if it works with various shells:
$ ps -t$(tty) -opid="" -ocommand="" | grep "sh$" | sort -k1,1nr | head -1 | sed 's/.* //'
ksh
Kornshell is fine.
$ ps -t$(tty) -opid="" -ocommand="" | grep "sh$" | sort -k1,1nr | head -1 | sed 's/.* //'
zsh
Works with zsh
$ ps -t$(tty) -opid="" -ocommand="" | grep "sh$" | sort -k1,1nr | head -1 | sed 's/.* //'
sh
Works with Dash or Ash
% ps -t$(tty) -opid="" -ocommand="" | grep "sh$" | sort -k1,1nr | head -1 | sed 's/.* //'
Illegal variable name.
Doesn't work in tcsh. Oh well... You can't please everybody.
#rici's approach is the most robust and generic solution.
A simpler, zsh-specific approach would be to use:
# Define a `which` alias only when NOT run by `zsh`.
[ -z $ZSH_VERSION ] && alias which ...
Some background information on how to determine the specific shell [binary] that is currently executing:
A robust option is to call the ps utility, relying on the fact POSIX-like shells as well as csh / tcsh report their own PID (process ID) via built-in variable $$:
ps -p $$ -o "comm="
Note: ps implementations differ with respect to what path form they report; e.g.:
macOS reports the binary path as invoked, which may be the mere file name, the full path, or even a relative path; additionally, if the shell was invoked as a login shell, such as via sudo -i, the mere file name can be prefixed with - (e.g., -bash).
by contrast, the procps-ng 3.3.12 ps implementation that comes with Ubuntu 18.04 only ever reports the file name, and never with the - prefix.
Alternatively, for scripts that are being sourced, as initialization files are, as well as interactive shells, you can examine the value of $0; note, however, that this is not foolproof, because the caller may have set $0 to an arbitrary value.
It points to the shell binary as invoked, i.e., the value may be either a mere filename or a path.
The value may be prefixed with -, namely if the shell is a login shell; e.g., -bash in a shell started with sudo -i.
Note: Do NOT use $SHELL, as it only ever reflects the current user's default shell. Its value doesn't change even when running other shells later.
Thus, a POSIX-compatible way of obtaining the current shell's executable filename is:
basename -- "${0#-}" # -> (e.g., in bash) 'bash'; will NOT work in csh/tcsh
Examples:
currShell=$(basename -- "${0#-}") # Store shell-binary filename in variable.
[ "$(basename -- "${0#-}")" = 'zsh' ] && echo "This is a ZSH shell."
If it is sufficient to test for a specific shell only, you may be able to simply test for the presence of specific environment variables such as $BASH_VERSION, $ZSH_VERSION, or $KSH_VERSION.
Note, however, that not all shells have such characteristic variables; dash, for instance, does not.
Example:
[ -n "$ZSH_VERSION" ] && echo "This is a ZSH shell."
The SHELL environment variable contains the full path to the binary of your shell. You could use that (or its basename):
s=$(basename $SHELL)
[ "$s" = 'zsh' ] || alias which="what you want it to be"

How to correctly wrap multiple command calls in bash?

My problem can be summed up by making this simple command works :
nice -n 10 "ls|xargs -I% echo \"%\""
Which fails :
nice: ls|xargs -I% echo "%": No such file or directory
Removing the quotes makes it works, but my point is to wrap multiple quoted commands into one to do something more complex like :
ftphost="192.168.1.1"
dirinputtopush="/tmp/archivedir/"
ftpoutputdir="mydir/"
nice -n 19 ls $dirinputtopush | xargs -I% "lftp $ftphost -e \"mirror -R $dirinputtopush% $ftpoutputdirrecent ;quit\"; sleep 10"
Try using nice -n 10 bash -c 'your; commands | or_complex pipelines' as command. This way bash is the binary and the string after -c contains a sequence interpreted by bash so it can contain pipelines, loops etc. Watch out for proper quoting. You need to do it this way because nice expects a binary, not expressions interpreted by the shell. In contrast, shell builtins such as time (but not /usr/bin/time which is a separate binary) will accept shell expressions as the command to execute. They can because they're built into the shell. nice is not, so it requires a binary to execute.
Children inherit nice value:
nice -n 10 bash -c 'ls | xargs -I% echo %'
Nice each command separately:
nice -n 10 ls | nice -n 10 xargs -I% echo %

How to remove inherited functions in sh (posix)

How do I ensure that there are no unexpected functions inherited from the parent when my script is run? If using bash,
#!/bin/bash -p
will do the trick, as will invoking the script through env -i. But I cannot rely on the user to invoke env, I don't want to rely on bash, I don't want to do an exec-hack and re-exec the script, and
#!/usr/bin/env -i sh
does not work.
So I'm looking for a portable way (portable == posix) to ensure that the user hasn't defined functions that will unexpectedly modify the behavior of the script. My current solution is:
eval $( env | sed -n '/\([^=]*\)=(.*/s//\1/p' |
while read -r name; do echo unset -f $name\;; done )
but that's pretty ugly and of dubious robustness. Is there a good way to get the functionality that 'unset -f -a' should provide?
edit
Slightly less ugly, but no better (I don't like parsing the output of env):
unset -f $( env | sed -n '/\([^=]*\)=(.*/s//\1/p' | tr \\012 \ )
#!/bin/bash --posix
results in:
SHELLOPTS=braceexpand:hashall:interactive-comments:posix
same as:
#!/bin/sh
SHELLOPTS=braceexpand:hashall:interactive-comments:posix
and "sh" is posix...
EDIT:
tested a few functions - unset was not required in my case...
EDIT2:
compare output of "set", not just "env"
EDIT3:
the following example - output of both "set|wc" also gives same results:
#!/bin/sh
set
set|wc
unset -f $( env | sed -n '/\([^=]*\)=(.*/s//\1/p' | tr \\012 \ )
set
set|wc
How about using the following env shebang line that sets a reasonable PATH variable to invoke the sh interpreter:
#!/usr/bin/env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/xpg4/bin sh

Resources