好日子。
我有一个小问题。我在 bash 脚本中有这个函数
function denyCheck(){
file=$(readlink -f $(which $(printf "%s\n" "$1")))
if [[ -x $file ]];then
return false
else
return true
fi
}
denyCheck "firefox"
这个函数我向她传递了一个字符串,什么是命令,这解析了命令所在的原始路径(例如:/usr/lib/firefox/firefox.sh),如果文件可执行,则返回 true,否则返回 false。
但问题是...参数(在示例中为“firefox”)将其作为命令运行并打开浏览器firefox。
我该如何做才能使参数不运行它?
非常感谢。
答案1
更应该是:
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
然后使用例如:
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
在 shell 中,所有命令都有一个退出状态,指示成功 (0) 或失败(任何其他数字,其值可以帮助区分不同类型的失败)。
例如,如果执行命令的进程具有文件(或目录)的执行权限,则[
命令在传递-x
和作为参数时将返回 true。/path/to/some/file
]
/path/to/some/file
函数还可以有退出状态,这是它返回之前运行的最后一个命令的退出状态(或者return
在调用时传递到的数字)。
命令的成功或失败状态由 shell 结构(例如if
//语句或其布尔运算符 和 )until
进行检查。while
||
&&
命令如果您愿意,可以是 shell 的布尔值。
cmd1 && cmd2 && cmd3
返回 success / true 是 的全部cmd1
,cmd2
并且cmd3
成功。如果失败cmd2
甚至不会执行cmd1
,对于cmd3
.
cmd_canonical_path
因此,如果command -v -- "$1"
(用于查找命令并输出其路径的标准内置 1 命令;而是which
一些类似的命令,但仅适用于 csh 用户),则上述成功和那么readlink -e
(其工作方式类似于readlink -f
规范化路径,但如果不能,则会返回失败,同时readlink -f
会尽最大努力)成功和那么当前进程有该文件的执行权限,和然后(并且只有到那时)该路径才成功分配给REPLY
4变量(普通分配总是成功的)。
cmd && cmd && cmd
如果没有,该函数将返回失败:该链中失败的第一个命令的退出状态。
您还会注意到,$REPLY
如果无法确定命令的规范路径或者您无法执行该命令,则该命令将被保留且不会被修改。
您的方法还有一些其他问题:
- 这
$(printf '%s\n' "$1")
没有多大意义。在 sh/bash 中,它与$1
(至少与默认值)相同,$IFS
并且同样错误,因为它没有被引用。如果要将第一个位置参数的内容传递给命令,语法为cmd "$1"
。 - 当您希望传递给命令的某些任意数据被视为非选项参数时,您需要在
--
标记选项结束的参数之后传递它:which -- "$1"
,readlink -f -- "$(...)"
。 return
接受一个(可选)数字作为参数(函数的退出代码),而不是true
/false
(你似乎也将它们颠倒了,因为你说如果文件可执行,函数将返回 true )。如果未传递数字,函数的退出状态将是函数中运行的最后一个命令的退出状态。所以if cmd; then return 0; else return 1; fi
最好写成 justcmd; return
。虽然在这里你甚至不需要return
因为cmd
([[ -x $file ]]
在你的情况下)是你的函数中的最后一个命令。- 您盲目地将 的输出传递给
which
(错误地,因为您忘记了--
并引用了$(...)
),而readlink
没有首先检查它是否成功。
最后一点,如果您使用zsh
代替bash
,您可以这样做:
firefox_path==firefox
firefox_canonical_path=$firefox_path:P
获取firefox
命令的路径。=firefox
扩展为命令的路径firefox
,如果找不到命令,则中止 shell 并显示错误。您可以拦截该错误always
堵塞如果需要的话。该:P
修饰符realpath()
对其所应用的参数执行等效操作,类似于 GNU 的操作readlink -f
。无需检查执行权限,因为zsh
's=cmd
运算符只会扩展到可执行路径。
¹ 由于它是 shell 的内置组件,因此您可以在 shell 的手册中找到它的文档。对于bash
,请参见info bash command
示例。
² 需要注意的是,command -v cmd
输出cmd
是cmd
一个函数或 shell 内置函数,这可能是一个问题。
³ 规范的绝对路径以 开头/
,没有符号链接组件(它们都已解析为目标),没有.
/组件,没有两个或多个s..
的序列。/
4 $REPLY
通常用作函数或 shell 内置函数返回值的默认变量(尽管这只是一种约定)。或者,您的函数可以输出结果,调用者将运行firefox_path=$(cmd_canonical_path firefox)
.
答案2
这:
function denyCheck(){
func()
是标准函数声明和 ksh 的混合function func
。 Bash 支持这种组合,其他 shell 可能不支持,但没有理由这样做。就写denyCheck()
吧。
这里,
file=$(readlink -f $(which $(printf "%s\n" "$1")))
这$(printf ...)
是不必要的,而且由于它没有被引用,实际上会适得其反。该命令替换的结果将受到分词,而引用的"$1"
不是,您可以使用它来代替。
which
被认为存在一些问题,标准 POSIX 替代方案是command -v
,请参阅为什么不用“哪个”呢?那该用什么呢?
两者之间的最大区别在于,它command
是一个内置的 shell,并且知道例如别名(如果您从交互式 shell 运行它),而它which
不知道(csh 除外,但您没有使用它)。这可能是好事,也可能不是好事:例如command -v ls
可能会 return alias ls='ls -vF --color=auto'
,这不是您可以使用的路径。
在任何情况下,您都应该引用命令替换以避免分词问题,并添加--
以标记选项的结尾。这是为了防止罕见的路径包含空格或以-
.那么那就是:
file=$(readlink -f -- "$(command -v -- "$1")") # or
file=$(readlink -f -- "$(which -- "$1")")
然后,
return false
只是一个错误:该return
命令需要一个数字。但是您可以运行名为的命令false
来获取错误的返回状态。所以要么使用
return 1
或者只是false
. (由于您只是反转测试结果,因此您也可以使用 just! [[ -x "$file" ]]
而不是整个if
语句,将其写出来可能会让下一个读者更清楚。)
所以:
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
}