是否可以操纵 shell 输入来包装命令?

是否可以操纵 shell 输入来包装命令?

我想在输入命令时调用一个函数并对其进行修改,
例如当用户输入此命令时

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。sudosudoers

不管怎样,您可能想要一个可以自动找到正确用户的解决方案;但是盲目地修改每个命令可能会导致事故发生,或者至少在您尝试使用 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,不可移植。然而,没有一种完美的便携方法来获取文件所有者

用法:

如果您正在使用 bash 完成,请运行complete -F _command s以使完成功能能够正常地工作s


迈向真正的自动化解决方案

有多种方法可以s自动对每个命令行使用(或其他)(例如Bash“虚拟”前缀终端)。请注意,s cd …s if …无法正常工作,因此在每个命令行前面添加s肯定会破坏您的工作流程。

some command(s)如果你真的想自动执行此操作,那么可以考虑转换为的自动化s bash -c 'some command(s)'。以下解决方案基于我对如何方便地在 Bash 中单引号或转义整个命令行?

首先定义一个将命令行转换为Ctrl+ xCtrl+ 的函数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-pasteon(见bind -v | grep enable-bracketed-paste)。你需要记住:

  • 键入的行将传递到单独的 shell,因此不会影响当前的 shell(例如,cd ~; pwd将按预期工作,但当前工作目录你的交互式 shell 不会受到影响);
  • 每次调用都是独立的(例如foo=bar Enter,然后echo "$foo" Enter就不会向您显示bar);
  • 你当前的 shell 不会扩展任何内容(除非你事先请求,例如在输入时使用Ctrl++Alte);
  • 键入时的操作(例如完成)将在您当前的 shell 中工作,但不能保证它在其他用户的交互式 shell 中以相同的方式工作。

您可以使用Ctrl+jShift+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"'

现在:

  • EnterCtrl+m正常执行命令行;
  • Shift如果需要,则+EnterCtrl+j注入sudo -u … bash -c(即,如果当前工作目录的所有者不是您)。

笔记:

  • 如果逐字嵌入到命令行中,则任何值都$user应该是安全的(我的意思是我们不需要${user@Q})。

  • 如果目录由没有名称的 UID 拥有,则GNUstat -c %U ./返回;您可能需要构建一些额外的逻辑来捕获此问题。UNKNOWN

  • 您仍然可以根据需要独立使用我们的功能s,例如:

    • … | … Enter将按照你的意思执行一切;
    • … | … Shift+Enter将以当前工作目录的所有者身份执行所有操作;
    • … | s … Enter将以您的身份执行管道的第一部分,s以目录所有者的身份执行第二部分(之后)。
  • 多行命令可能会很麻烦。如果与带有不平衡引号的多行命令一起使用, Shift+Enter可能会出现错误。我承认,我曾通过在带有平衡引号的多行命令上使用Shift+导致 Bash 5.2.15 崩溃Enter

根据您的需要调整解决方案。

相关内容