21

I use Bash as my interactive shell and I was wondering if there was an easy way to get Bash to run a system command instead of a shell builtin command in the case where they both share the same name.

For example, use the system kill (from util-linux) to print the process id (pid) of the named process(es) instead of sending a signal:

$ /bin/kill -p httpd
2617
...

Without specifying the full path of the system command, the Bash builtin is used instead of the system command. The kill builtin doesn’t have the -p option so the command fails:

$ kill -p httpd
bash: kill: p: invalid signal specification

I tried the answers listed in Make bash use external `time` command rather than shell built-in but most of them only work because time is actually a shell keyword – not a shell builtin.

Other than temporarily disabling the Bash builtin with enable -n kill, the best solution I’ve seen so far is to use:

$(which kill) -p httpd

Are there other easier (involve less typing) ways to execute an external command instead of a shell builtin?

Note that kill is just an example and I’d like a generalised solution similar to the way that prefixing with the command builtin prevents functions which have the same name as an external command from being run. In most cases, I usually prefer to use the builtin version as it saves forking a new process and some times the builtin has features that the external command doesn’t.

6 Answers6

25

Assuming env is in your path:

env kill -p http

env runs the executable file named by its first argument in a (possibly) modified environment; as such, it does not know about or work with shell built-in commands.

This produces some shell job control cruft, but doesn't rely on an external command:

exec kill -p bash &

exec requires an executable to replace the current shell, so doesn't use any built-ins. The job is run in the background so that you replace the forked background shell, not your current shell.

chepner
  • 7,501
  • 2
    env is clearly the right answer IMHO. – abligh Nov 12 '15 at 14:38
  • @abligh: You're right in comment in my deleted answer. command -p cmd calling external command in zsh, not bash. – cuonglm Nov 12 '15 at 15:39
  • 8
    (exec kill -p http) causes the job to replace a sub-shell instead of your current shell and you don't have to deal with the job control cruft. – Michael Hoffman Nov 12 '15 at 23:44
  • 1
    @AnthonyGeoghegan Indeed, but env also doesn't know about shell builtins, since it's not a shell. You'd get the same effect with nice or xargs or any other program like that. – user253751 Nov 13 '15 at 00:05
1

The simplest way to do what you want might be to put the line

alias kill="/bin/kill"

into your ~/.bashrc file. After that, each new login/invocation of bash will interpret "kill" as /bin/kill.

cuonglm
  • 153,898
Benjamin Staton
  • 501
  • 2
  • 9
  • I had already thought of an alias and that was one of the solutions listed in the question I linked to but I’m hoping for a more generalised solution (similar to the command solution) rather than create a separate alias for each “shadowed” command. I’ve now edited my question to make this clearer and more explicit. In most cases, I usually prefer to use the builtin version as processes can be specified by job IDs. Thanks, anyway. – Anthony Geoghegan Nov 12 '15 at 13:26
1

If you know a solution that requires some typing and you want a solution that requries less typing, build it:

runFile() { local cmd="$1"; shift; cmd="$(which "$cmd")" && "$cmd" "$@"; }

Abbreviating stuff that normally takes some effort is what computers excel at.

Petr Skocik
  • 28,816
  • 1
    That's a good general point so I'll upvote your answer. Over the past few years, I've built up a collection of aliases, functions and scripts for the systems that I regularly use. However, I prefer to use non-customised solutions where possible as I sometimes have to work on fresh installs or servers that I didn't set up. Thanks. – Anthony Geoghegan Nov 12 '15 at 20:34
1

In this very specific case, the command pgrep is an exact match for the need.

In a general sense, a function works. From "file command":

fcmd(){ local a=$1; shift; $(which "$a") "$@"; }

call as

fcmd kill -p httpd

But if you need less typing, there is no shorter way than a good alias.

From the concept "list pid" (lp):

alias lp='/bin/kill -p'

then, just type:

lp httpd
0

(In zsh) You can prefix any command name with an = sign to get the system version instead of a builtin. This is also a handy way to dodge any aliases that mess up a specific scenario.

$ =kill -p httpd
Caleb
  • 70,105
  • 1
    I tried that with Bash version 4.3.30 and it didn't work. I've never seen such syntax before; can you add a link to where this feature is documented? I'd normally prefix an alias with a backslash to run the non-aliased version of a command. – Anthony Geoghegan Nov 12 '15 at 20:28
  • @Anthony maybe this is a zsh only thing. I use it regularly but I'll check on bash from a computer tomorrow. – Caleb Nov 12 '15 at 20:31
  • I suspected it might be some shell that I'm not familiar with (I’m only familiar with bash, dash and csh). There was another answer (since deleted) that only worked for zsh. While I tagged this question with bash and used it in the title, you should keep this answer as a zsh user would still find it useful. – Anthony Geoghegan Nov 12 '15 at 20:41
-2

You could send a bugreport against your kill manpage and ask why this includes non-standard options that are taken from pkill and use pkill whenever you like to get the features from pkill.

If you call:

pkill httpd

you avoid the problems you describe.

schily
  • 19,173