1

Good days.

I have a little problem. I have this function in bash script

function denyCheck(){
    file=$(readlink -f $(which $(printf "%s\n" "$1")))
    if [[ -x $file ]];then
           return false
    else
           return true
    fi
}
denyCheck "firefox"

This function I pass her a string, what is a comman, and this resolv the orginal path where is the command (by example: /usr/lib/firefox/firefox.sh) and if the file is executable return true else false.

But the problem is...The parameter (in the example "firefox") run it as command and open the browser firefox.

How can I do for that the parameter not run it?

Thank you very much.

cleanet
  • 13
  • 3

2 Answers2

0

Should rather be:

cmd_canonical_path() {
  local cmd_path
  cmd_path=$(command -v -- "$1") &&
    cmd_path=$(readlink -e -- "$cmd_path") &&
    [ -x "$cmd_path" ] &&
    REPLY=$cmd_path
} 2> /dev/null

And then use for instance as:

if cmd_canonical_path firefox; then
  printf '%s\n' "firefox found in $REPLY executable"
else
  printf >&2 '%s\n' "no executable file found for firefox"
fi

In shells, all commands have an exit status that indicates either success (0) or failure (any other number whose values can help discriminate between different types of failure).

For instance, the [ command, when passed -x, /path/to/some/file and ] as arguments will return true if the process that executes it has execute permission for the /path/to/some/file file (or directory).

A function can also have an exit status, and that's the exit status of the last command it ran before it returned (or the number passed to return if called there).

The success or failure status of commands is checked by shell constructs such as if/until/while statements or its boolean operators || and &&. Commands are the shells' booleans if you like.

cmd1 && cmd2 && cmd3 returns success / true is all of cmd1, cmd2 and cmd3 succeed. And cmd2 is not even executed if cmd1 fails, same for cmd3.

So above cmd_canonical_path succeeds if command -v -- "$1" (the standard builtin¹ command to lookup a command and output its path²; while which was some similar command but intended for csh users only) and then that readlink -e (which works like readlink -f to canonicalise³ a path but will return failure if it can't while readlink -f will do a best effort) succeeds and then that the current process has execute permission for that file, and then (and only then) that that path is successfully assigned to the REPLY4 variable (plain assignments are always successful).

If not, the function will return failure: the exit status of the first command that failed in that cmd && cmd && cmd chain.

You'll also notice that $REPLY is left alone and not modified if the canonical path to the command cannot be determined or it's not executable by you.

Some other problems with your approach:

  • That $(printf '%s\n' "$1") doesn't make much sense. In sh/bash, it's the same as $1 (at least with the default value of $IFS) and is equally wrong as it's not quoted. If you want to pass the contents of the first positional parameter to a command, the syntax is cmd "$1".
  • When you want some arbitrary data you pass to a command to be treated as a non-option arguments, you need to pass it after the -- argument that marks the end of options: which -- "$1", readlink -f -- "$(...)".
  • return takes a (optional) number as argument (the exit code for the function), not true/false (you also seem to have them reversed as you said the function was to return true if the file was executable). If not passed a number, the function's exit status will be that of the last command run in the function. So if cmd; then return 0; else return 1; fi is better written as just cmd; return. Though here you don't even need the return since cmd ([[ -x $file ]] in your case) is the last command in your function.
  • You're blindly passing the output of which (incorrectly as you forgot the -- and to quote the $(...)) to readlink without having first checked that it succeeded.

As a final note, had you used zsh instead of bash, you could have done:

firefox_path==firefox
firefox_canonical_path=$firefox_path:P

To get the path of the firefox command. =firefox expands to the path of the firefox command and aborts the shell with an error if it can't be found. You can intercept that error in an always block if need be. The :P modifier does a realpath() equivalent on the parameter it's applied to, similar to what GNU readlink -f does. No need to check for execute permissions as zsh's =cmd operator would only expand to a path that is executable.


¹ and as it's a builtin of the shell, you'll find its documentation in the shell's manual. For bash, see info bash command for instance.

² with the caveat that command -v cmd outputs cmd is cmd is a function or shell builtin which could be a problem here.

³ a canonical absolute path starts with /, has no symlink component (they've all be resolved to their target), no . / .. component, no sequences of two or more /s.

4 $REPLY is commonly used (though that's only a convention) as the default variable for a function or shell builtin to return a value in. Alternatively, your function could output the result, and the caller would run firefox_path=$(cmd_canonical_path firefox).

0

This:

function denyCheck(){

is a mix of the standard function declaration func(), and ksh's function func. Bash supports the combination, other shells might not, but there's no reason to do it. Just write denyCheck() instead.

Here,

file=$(readlink -f $(which $(printf "%s\n" "$1")))

that $(printf ...) is unnecessary, and since it's unquoted, actually counterproductive. The result of that command substitution would be subject to word splitting, while the quoted "$1" isn't, and you could just use that instead instead.

which is considered to have some issues, and the standard POSIX alternative is command -v, see Why not use "which"? What to use then?

The big difference between the two is that command is a shell builtin, and knows about e.g. aliases (if you run it from an interactive shell), which which doesn't (except in csh but you're not using that). This may or may not be a good thing: e.g. command -v ls might return alias ls='ls -vF --color=auto', which isn't a path you can use.

In any case, you should to quote the command substitution to avoid issues with word splitting and add -- to mark the end of options. This is to guard against the rare path that contains whitespace or starts with a -. So that would be:

file=$(readlink -f -- "$(command -v -- "$1")")  # or
file=$(readlink -f -- "$(which -- "$1")") 

Then,

return false

is just an error: the return command takes a number. But you could just run the command called false to get a falsy return status. So either use return 1 or just false. (Since you're just inverting the result of the test, you could also use just ! [[ -x "$file" ]] instead of the whole if statement, writing it out might be clearer to the next reader.)

So:

denycheck() {
    file=$(readlink -f -- "$(command -v -- "$1")")
    if [[ -x "$file" ]]; then
        return 1  # command found and executable, falsy result
    else
        return 0  # not executable, truthy result
    fi
}
ilkkachu
  • 138,973