How to check if command is available or existant? - linux

I am developing a console application in C on linux.
Now an optional part of it (its not a requirement) is dependant on a command/binary being available.
If I check with system() I'm getting sh: command not found as unwanted output and it detects it as existent. So how would I check if the command is there?
Not a duplicate of Check if a program exists from a Bash script since I'm working with C, not BASH.

To answer your question about how to discover if the command exists with your code. You can try checking the return value.
int ret = system("ls --version > /dev/null 2>&1"); //The redirect to /dev/null ensures that your program does not produce the output of these commands.
if (ret == 0) {
//The executable was found.
}
You could also use popen, to read the output. Combining that with the whereis and type commands suggested in other answers -
char result[255];
FILE* fp = popen("whereis command", "r");
fgets(result, 255, fp);
//parse result to see the path of the bin if it has been found.
pclose(check);
Or using type:
FILE* fp = popen("type command" , "r");
The result of the type command is a bit harder to parse since it's output varies depending on what you are looking for (binary, alias, function, not found).

You can use stat(2) on Linux(or any POSIX OS) to check for a file's existence.

Use which, you can either check the value returned by system() (0 if found) or the output of the command (no output equal not found):
$ which which
/usr/bin/which
$ echo $?
0
$ which does_t_exist
$ echo $?
1

If you run a shell, the output from "type commandname" will tell you whether commandname is available, and if so, how it is provided (alias, function, path to binary). You can read the documentation for type here: http://ss64.com/bash/type.html

I would just go through the current PATH and see whether you can find it there. That’s what I did recently with an optional part of a program that needed agrep installed. Alternately, if you don’t trust the PATH but have your own list of paths to check instead, use that.
I doubt it’s something that you need to check with the shell for whether it’s a builtin.

Related

How do I write a file path which include a regular expression

Depending on the system I am working on, there might be 2 different possible paths (mutually exclusive):
System1: /tmp/aword/foo
System2: /tmp/bword/foo
I am supposed to echo something into the foo file regardless of which system I encounter (through a shell script).
How do I include a regular expression within the path itself, to take the correct (existent) path?
somethings I have tried:
#doesn't work
echo Hello > /tmp/(a|b)word/foo
#doesn't work
echo Hello > /tmp/[a|b]word/foo
is there a way of doing this without having to include a test before this which tests for path existence?
If it literally is aword and bword and you know that only one of them exists, you can use
echo 'Hello' > /tmp/[ab]word/foo
This is a shell pattern and documented in the Bash manual or the POSIX sh spec.
If, however, both paths exist, Bash will complain with
-bash: [ab]word: ambiguous redirect

Bash does not print any error msg upon non-existing commands starting with dot

This is really just out of curiosity.
A typo made me notice that in Bash, the following:
$ .anything
does not print any error ("anything" not to be interpreted literally, it can really be anything, and no space after the dot).
I am curious about how this is interpreted in bash.
Note that echo $? after such command returns 127. This usually means "command not found". It does make sense in this case, however I find it odd that no error message is printed.
Why would $ anything actually print bash:anything: command not found... (assuming that no anything cmd is in the PATH), while $ .anything slips through silently?
System: Fedora Core 22
Bash version: GNU bash, version 4.3.39(1)-release (x86_64-redhat-linux-gnu)
EDIT:
Some comments below indicated the problem as non-reproducible at first.
The answer of #hek2mgl below summarises the many contributions to this issue, which was eventually found (by #n.m.) as reproducible in FC22 and submitted as a bug report in https://bugzilla.redhat.com/show_bug.cgi?id=1292531
bash supports a handler for situations when a command can't be found. You can define the following function:
function command_not_found_handle() {
command=$1
# do something
}
Using that function it is possible to suppress the error message. Search for that function in your bash startup files.
Another way to find that out is to unset the function. Like this:
$ unset -f command_not_found_handle
$ .anything # Should display the error message
After some research, #n.m. found out that the described behaviour is by intention. FC22 implements command_not_found_handle and calls the program /etc/libexec/pk-command-not-found. This program is part of the PackageKit project and will try to suggest installable packages if you type a command name that can't be found.
In it's main() function the program explicitly checks if the command name starts with a dot and silently returns in that case. This behaviour was introduced in this commit:
https://github.com/hughsie/PackageKit/commit/0e85001b
as a response to this bug report:
https://bugzilla.redhat.com/show_bug.cgi?id=1151185
IMHO this behaviour is questionable. At least other distros are not doing so. But now you know that the behaviour is 100% reproducible and you may follow up on that bug report.

'less' the file specified by the output of 'which'

command 'which' shows the link to a command.
command 'less' open the file.
How can I 'less' the file as the output of 'which'?
I don't want to use two commands like below to do it.
=>which script
/file/to/script/fiel
=>less /file/to/script/fiel
This is a use case for command substitution:
less -- "$(which commandname)"
That said, if your shell is bash, consider using type -P instead, which (unlike the external command which) is built into the shell:
less -- "$(type -P commandname)"
Note the quotes: These are important for reliable operation. Without them, the command may not work correctly if the filename contains characters inside IFS (by default, whitespace) or can be evaluated as a glob expression.
The double dashes are likewise there for correctness: Any argument after them is treated as positional (as per POSIX Utility Syntax Guidelines), so even if a filename starting with a dash were to be returned (however unlikely this may be), it ensures that less treats that as a filename rather than as the beginning of a sequence of options or flags.
You may also wish to consider honoring the user's pager selection via the environment variable $PAGER, and using type without -P to look for aliases, shell functions and builtins:
cmdsource() {
local sourcefile
if sourcefile="$(type -P -- "$1")"; then
"${PAGER:-less}" -- "$sourcefile"
else
echo "Unable to find source for $1" >&2
echo "...checking for a shell builtin:" >&2
type -- "$1"
fi
}
This defines a function you can run:
cmdsource commandname
You should be able to just pipe it over, try this:
which script | less

How to prevent execution of command in ZSH?

I wrote hook for command line:
# Transforms command 'ls?' to 'man ls'
function question_to_man() {
if [[ $2 =~ '^\w+\?$' ]]; then
man ${2[0,-2]}
fi
}
autoload -Uz add-zsh-hook
add-zsh-hook preexec question_to_man
But when I do:
> ls?
After exiting from man I get:
> zsh: no matches found: ls?
How can I get rid of from message about wrong command?
? is special to zsh and is the wildcard for a single character. That means that if you type ls? zsh tries find matching file names in the current directory (any three letter name starting with "ls").
There are two ways to work around that:
You can make "?" "unspecial" by quoting it: ls\?, 'ls?' or "ls?".
You make zsh handle the cases where it does not match better:
The default behaviour if no match can be found is to print an error. This can be changed by disabling the NOMATCH option (also NULL_GLOB must not be set):
setopt NO_NOMATCH
setopt NO_NULL_GLOB
This will leave the word untouched, if there is no matching file.
Caution: In the (maybe unlikely) case that there is a file with a matching name, zsh will try to execute a command with the name of the first matching file. That is if there is a file named "lsx", then ls? will be replaced by lsx and zsh will try to run it. This may or may not fail, but will most likely not be the desired effect.
Both methods have their pro and cons. 1. is probably not exactly what you are looking for and 2. does not work every time as well as changes your shells behaviour.
Also (as #chepner noted in his comment) preexec runs additionally to not instead of a command. That means you may get the help for ls but zsh will still try to run ls? or even lsx (or another matching name).
To avoid that, I would suggest defining a command_not_found_handler function instead of preexec. From the zsh manual:
If no external command is found but a function command_not_found_handler exists the shell executes this function with all command line arguments. The function should return status zero if it successfully handled the command, or non-zero status if it failed. In the latter case the standard handling is applied: ‘command not found’ is printed to standard error and the shell exits with status 127. Note that the handler is executed in a subshell forked to execute an external command, hence changes to directories, shell parameters, etc. have no effect on the main shell.
So this should do the trick:
command_not_found_handler () {
if [[ $1 =~ '\?$' ]]; then
man ${1%\?}
return 0
else
return 1
fi
}
If you have a lot of matching file names but seldomly misstype commands (the usual reason for "Command not found" errors) you might want to consider using this instead:
command_not_found_handler () {
man ${1%?}
}
This does not check for "?" at the end, but just cuts away any last character (note the missing "\" in ${1%?}) and tries to run man on the rest. So even if a file name matches, man will be run unless there is indeed a command with the same name as the matched file.
Note: This will interfere with other tools using command_not_found_handler for example the command-not-found tool from Ubuntu (if enabled for zsh).
That all being said, zsh has a widget called run-help which can be bound to a key (in Emacs mode it is by default bound to Alt+H) and than runs man for the current command.
The main advantages of using run-help over the above are:
You can call it any time while typing a longer command, as long as the command name is complete.
After you leave the manpage, the command is still there unchanged, so you can continue writing on it.
You can even bind it to Alt+? to make it more similar: bindkey '^[?' run-help

Bash script execution with and without shebang in Linux and BSD

How and who determines what executes when a Bash-like script is executed as a binary without a shebang?
I guess that running a normal script with shebang is handled with binfmt_script Linux module, which checks a shebang, parses command line and runs designated script interpreter.
But what happens when someone runs a script without a shebang? I've tested the direct execv approach and found out that there's no kernel magic in there - i.e. a file like that:
$ cat target-script
echo Hello
echo "bash: $BASH_VERSION"
echo "zsh: $ZSH_VERSION"
Running compiled C program that does just an execv call yields:
$ cat test-runner.c
void main() {
if (execv("./target-script", 0) == -1)
perror();
}
$ ./test-runner
./target-script: Exec format error
However, if I do the same thing from another shell script, it runs the target script using the same shell interpreter as the original one:
$ cat test-runner.bash
#!/bin/bash
./target-script
$ ./test-runner.bash
Hello
bash: 4.1.0(1)-release
zsh:
If I do the same trick with other shells (for example, Debian's default sh - /bin/dash), it also works:
$ cat test-runner.dash
#!/bin/dash
./target-script
$ ./test-runner.dash
Hello
bash:
zsh:
Mysteriously, it doesn't quite work as expected with zsh and doesn't follow the general scheme. Looks like zsh executed /bin/sh on such files after all:
greycat#burrow-debian ~/z/test-runner $ cat test-runner.zsh
#!/bin/zsh
echo ZSH_VERSION=$ZSH_VERSION
./target-script
greycat#burrow-debian ~/z/test-runner $ ./test-runner.zsh
ZSH_VERSION=4.3.10
Hello
bash:
zsh:
Note that ZSH_VERSION in parent script worked, while ZSH_VERSION in child didn't!
How does a shell (Bash, dash) determines what gets executed when there's no shebang? I've tried to dig up that place in Bash/dash sources, but, alas, looks like I'm kind of lost in there. Can anyone shed some light on the magic that determines whether the target file without shebang should be executed as script or as a binary in Bash/dash? Or may be there is some sort of interaction with kernel / libc and then I'd welcome explanations on how does it work in Linux and FreeBSD kernels / libcs?
Since this happens in dash and dash is simpler, I looked there first.
Seems like exec.c is the place to look, and the relevant functionis are tryexec, which is called from shellexec which is called whenever the shell things a command needs to be executed. And (a simplified version of) the tryexec function is as follows:
STATIC void
tryexec(char *cmd, char **argv, char **envp)
{
char *const path_bshell = _PATH_BSHELL;
repeat:
execve(cmd, argv, envp);
if (cmd != path_bshell && errno == ENOEXEC) {
*argv-- = cmd;
*argv = cmd = path_bshell;
goto repeat;
}
}
So, it simply always replaces the command to execute with the path to itself (_PATH_BSHELL defaults to "/bin/sh") if ENOEXEC occurs. There's really no magic here.
I find that FreeBSD exhibits identical behavior in bash and in its own sh.
The way bash handles this is similar but much more complicated. If you want to look in to it further I recommend reading bash's execute_command.c and looking specifically at execute_shell_script and then shell_execve. The comments are quite descriptive.
(Looks like Sorpigal has covered it but I've already typed this up and it may be of interest.)
According to Section 3.16 of the Unix FAQ, the shell first looks at the magic number (first two bytes of the file). Some numbers indicate a binary executable; #! indicates that the rest of the line should be interpreted as a shebang. Otherwise, the shell tries to run it as a shell script.
Additionally, it seems that csh looks at the first byte, and if it's #, it'll try to run it as a csh script.

Resources