Check if a command exists in Bash (including superusers)
bash, shell
Solution
Source of the problem
The reason the commands you tried do not work is that they only look for executables in `$PATH` variable. First, let's test our hypothesis.
dummy:~$ mkdir test
dummy:~$ cd test
dummy:~/test$ echo '#!/bin/sh' >test.sh
dummy:~/test$ chmod +x test.sh
dummy:~/test$ cd
dummy:~$ command -v test.sh
dummy:~$ PATH+=:/home/dummy/test/
dummy:~$ command -v test.sh
/home/dummy/test/test.sh
This confirms my statement above. Now, let's have a look what `$PATH` looks like for different users:
dummy:~$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
dummy:~$ su
root:~# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
So in order to check whether given command is available to given user (in your question, namely: root), you need to know his `$PATH` environment variable.
The solution
Values of such environment variables on Debian can be usually found in `/etc/profile` and in `/etc/environment/` files. There is no easy way to get these values by fishing them out of files.
The most basic solution is to temporarily add known directories to your `$PATH` variable and then use `command -v`:
dummy~$ OLDPATH=$PATH
dummy~$ PATH=$OLDPATH:/sbin:/usr/sbin/:/usr/local/sbin/
dummy~$ command -v poweroff
/sbin/poweroff
dummy~$ PATH=$OLDPATH
There is one problem with this solution: if you want to be portable, you don't really know what are the folders that you should concatenate. In most cases this approach should be sufficient, though.
Alternative solution
What you can do instead is to write a script program that makes use of setuid bit. Setuid bit is a somewhat hidden feature of Linux operating systems that allows programs to be executed on their owner privileges. So you write a program that executes some commands like superuser would, except that it can be run by normal users. That way you can see output of `command -v poweroff` like a root would do.
Unfortunately, stuff that uses shebang can't have setuid bit, so you cannot create a shell script for this and you need a program in C. Here's an example program that would do the job:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char** argv)
{
if (argc <= 1)
{
fprintf(stderr, "No arguments.\n");
return 1;
}
//validate the argv
char* prog = argv[1];
int i;
for (i = 0; i < strlen(prog); i ++)
{
if (prog[i] < 'a' || prog[i] > 'z')
{
fprintf(stderr, "%s contains invalid characters (%c), exiting.", prog, prog[i]);
return 1;
}
}
//here's the `which` command. We start it in new interactive shell,
//since this program inherits environment variables from its
//parent shell. We need to start *new* shell that will initialize
//and overwrite existing PATH environment variable.
char* command = (char*) malloc(strlen(prog) + 30);
if (!command)
{
fprintf(stderr, "No memory!\n");
return 1;
}
sprintf(command, "bash -cli 'command -v %s'", prog);
int exists = 0;
//first we try to execute the command as a dummy user.
exists |= system(command) == 0;
if (!exists)
{
//then we try to execute the command as a root user.
setuid(0);
exists |= system(command) == 0;
}
return exists ? 0 : 1;
}
Security note: the version above has very simple argument validation (it lets through only strings matching `^[a-z]*$`). Real program should probably include better validation.
Testing
Suppose we saved the file in `test.c`. We compile it and add setuid bit:
root:~# gcc ./test.c -o ./test
root:~# chown root:root ./test
root:~# chmod 4755 ./test
Note that `chown` goes before `chmod`. The `4` before usual `755` pattern is the setuid bit. Now we can test the program as a normal user.
dummy:~$ ./test ls; echo $?
alias ls='ls -vhF1 --color=auto --group-directories-first'
0
dummy:~$ ./test blah; echo $?
1
dummy:~$ ./test poweroff; echo $?
/sbin/poweroff
0
And best of all - it's portable enough to work on cygwin with no problems. :)
Problem
I'd like to check whether a program is installed on an UNIX system. I could use commands like: - `command -v`, - `hash`, - `type`, - `which`... ...and all of them are already mentioned in this answer. However, none of them work if I want to test, as a normal user, whether I or any superuser can run given command. Here's an example of what I mean: ``` dummy:~$ command -v poweroff; echo $? 1 dummy:~$ su root:~# command -v poweroff; echo $? /sbin/poweroff 0 ``` As you see, normal user didn't find out about the existence of the `poweroff` command. Note that dummy users can freely see what's in `/sbin` anyway.