我想在输入命令时调用一个函数并对其进行修改,
例如当用户输入此命令时
touch foo.txt
我想操纵它运行
sudo -u user touch foo.txt
原因是我作为 root 拥有许多属于不同用户的目录,这些用户没有 shell 访问权限(所以我不能只使用 sudo 用户),并且很多时候我想以该用户的身份运行命令,我想编写脚本来检查当前目录所有者并以该用户的身份运行该命令。
谢谢。
答案1
初步说明
问题已加标签狂欢和鱼。这个答案是。我完全bash
不知道。fish
按需解决方案
没有 shell 访问权限的用户(所以我不能只使用 sudo 用户)
您可以执行sudo -u user touch …
,因此可能也可以执行sudo -u user bash
。即使对于登录 shell 为 或 的用户,这也可能有效。 (通常)/usr/sbin/nologin
的安全策略才是最重要的,而不是登录 shell。sudo
sudoers
不管怎样,您可能想要一个可以自动找到正确用户的解决方案;但是盲目地修改每个命令可能会导致事故发生,或者至少在您尝试使用 shell 关键字、内置或时带来不便sudo
。按需调用似乎s touch foo.txt
更好。
sh
这是或bash
任何符合 POSIX 标准的 shell 的shell 函数:
s() (
user="$(stat -c %U ./)"
if [ "$#" -eq 0 ]; then
set bash
fi
exec sudo -u "$user" -- "$@"
)
注意:该函数使用 GNU stat
,不可移植。然而,没有一种完美的便携方法来获取文件所有者。
用法:
s touch foo.txt
- 或者只是
s
为了获得一个交互式的 Bash。最终使用exit
或Ctrl+ d(如果需要的话可以重复两次)返回主 shell。
如果您正在使用 bash 完成,请运行complete -F _command s
以使完成功能能够正常地工作s
。
迈向真正的自动化解决方案
有多种方法可以s
自动对每个命令行使用(或其他)(例如Bash“虚拟”前缀终端)。请注意,s cd …
或s if …
无法正常工作,因此在每个命令行前面添加s
肯定会破坏您的工作流程。
some command(s)
如果你真的想自动执行此操作,那么可以考虑转换为的自动化s bash -c 'some command(s)'
。以下解决方案基于我对如何方便地在 Bash 中单引号或转义整个命令行?
首先定义一个将命令行转换为Ctrl+ x、Ctrl+ 的函数o:
_prepend_s() { READLINE_LINE="s bash -c ${READLINE_LINE@Q}"; }
bind -x '"\C-x\C-o":_prepend_s'
在我的 Kubuntu 中, Bash 以+ 的Enter形式出现,但+是等效的。非常方便,因为现在我们可以这样做:CtrlmCtrlj
bind '"\C-m":"\C-x\C-o\C-j"'
从现在起,将Enter转换some command(s)
为s bash -c 'some command(s)'
并自动执行结果。这将支持cd
、多个命令(使用;
、、)、管道等,但不支持多行代码(除非在粘贴代码时&&
||
if …
enable-bracketed-paste
是on
(见bind -v | grep enable-bracketed-paste
)。你需要记住:
- 键入的行将传递到单独的 shell,因此不会影响当前的 shell(例如,
cd ~; pwd
将按预期工作,但当前工作目录你的交互式 shell 不会受到影响); - 每次调用都是独立的(例如
foo=bar
Enter,然后echo "$foo"
Enter就不会向您显示bar
); - 你当前的 shell 不会扩展任何内容(除非你事先请求,例如在输入时使用Ctrl++Alt等e);
- 键入时的操作(例如完成)将在您当前的 shell 中工作,但不能保证它在其他用户的交互式 shell 中以相同的方式工作。
您可以使用Ctrl+j或Shift+Enter以当前用户身份直接在当前 shell 中执行命令行。
这种简单方法有一个缺点:命令存储在历史记录中,s bash -c …
如果你想重复执行该命令,↑Enter它将失败。快速修复可能是:
_prepend_s() { history -s "$READLINE_LINE"; READLINE_LINE=" s bash -c ${READLINE_LINE@Q}"; }
它将原始命令存储在历史记录中,而前导空格会阻止存储实际命令(此功能由HISTCONTROL
);还有其他方法可以解决这个问题。
我喜欢的解决方案
如果我是你,我更愿意看到完整的命令,包括sudo -u
(这样就可以清楚地知道它以哪个用户身份运行);并且我希望保留原来的功能Enter(以避免意外)。这就是我要使用的:
# IMPORTANT: do it in a fresh shell, so our previous bindings don't interfere
_act_as_dirowner() {
history -s "$READLINE_LINE"
local user
user="$(stat -c %U ./)"
[ "$user" != "$USER" ] && READLINE_LINE=" sudo -u $user bash -c ${READLINE_LINE@Q}";
}
bind -x '"\C-x\C-o":_act_as_dirowner'
bind '"\C-j":"\C-x\C-o\C-m"'
现在:
- Enter或Ctrl+m正常执行命令行;
- Shift如果需要,则+Enter或Ctrl+j注入
sudo -u … bash -c
(即,如果当前工作目录的所有者不是您)。
笔记:
如果逐字嵌入到命令行中,则任何值都
$user
应该是安全的(我的意思是我们不需要${user@Q}
)。如果目录由没有名称的 UID 拥有,则GNU
stat -c %U ./
返回;您可能需要构建一些额外的逻辑来捕获此问题。UNKNOWN
您仍然可以根据需要独立使用我们的功能
s
,例如:… | …
Enter将按照你的意思执行一切;… | …
Shift+Enter将以当前工作目录的所有者身份执行所有操作;… | s …
Enter将以您的身份执行管道的第一部分,s
以目录所有者的身份执行第二部分(之后)。
多行命令可能会很麻烦。如果与带有不平衡引号的多行命令一起使用, Shift+Enter可能会出现错误。我承认,我曾通过在带有平衡引号的多行命令上使用Shift+导致 Bash 5.2.15 崩溃Enter。
根据您的需要调整解决方案。