【问题标题】:sh -c irrationality and programmatically determining and running Linux builtin commandssh -c 非理性和以编程方式确定和运行 Linux 内置命令
【发布时间】:2016-07-11 07:35:49
【问题描述】:

我需要从程序中确定 Linux 命令是否是内置命令。我还需要偶尔运行该内置命令。我将以别名为例,它也适用于其他内置函数。

我找到的一个恰当的答案是使用:

sh -c 'type alias'

这将返回“别名是内置的 shell”,这正是我所需要的。但是...我也需要运行它。我尝试过的任何事情都不会这样做。所有这些都在终端窗口中失败(并以编程方式):

  • sh -c 'alias'(没有任何输出)
  • sh -c 'command alias'(没有任何输出)
  • sh -c 'builtin alias' (sh: 1: builtin: not found)
  • sh -c 'type builtin'(内置:未找到)
  • sh -c 'type command'(命令是内置的shell)
  • sh -c 'type type'(类型是内置的 shell)

(几乎)不用说,所有这些命令(即,没有sh -c)在终端上都可以正常工作。这让我对 sh -c 命令看似不合理的性质感到困惑。

我的问题是:所有 Linux 版本都是这样吗?我是否缺少开关或设置?那么,我怎样才能从一个程序中执行一个内置函数以取回它的输出呢?

我正在开发 Kubuntu 14.04(在 Trisquel 中也是如此);在konsolexterm 以及程序调用中都会出现此问题。


感谢您的cmets和答案,非常感谢。

我确实需要澄清我在做什么:我正在编写一个 CLI 辅助 GUI,一个用于存储和执行最喜欢的 linux 命令的程序。

我使用 freepascal / Lazarus,它有一个 TProcess 类,它启动一个进程并提供对标准输入、标准输出和标准错误的访问权限。

这非常适用于基于文件的命令(例如 rsync 等),并且我在输入和输出文本时没有问题。我什至可以复制管道,只需将输出从一个进程传递到另一个进程的输入。但是……

...内置的不是这样。这让我走上了研究别名等的道路。

我希望我的程序也包含内置的,但第一个问题是如何在添加时判断它是否是内置的(是的,内置的列表是有限的,但以后可以扩展它,我想要我的程序来处理这种可能性)。因此我研究并发现了 sh -c 'type cmd'。

现在我可以确定命令是基于文件(使用哪个)还是内置(使用上面的表达式)。

虽然通过我的程序运行内置程序不会有很大的需求,但偶尔从一个程序中获取输出很有用。例如,别名。不带参数运行时的别名等效于 alias -p ,它只是输出系统中注册的当前别名。

但这就是障碍所在。如上所示,我似乎无法获得输出。

我现在明白了 subshel​​l 的概念,这就解释了为什么我什么也没得到(我怀疑它与输出有关)。

很遗憾,到目前为止所有建议都不起作用。例如, eval 命令(谢谢你,我不知道那个)。它是内置的。所以我知道(尝试)获得输出的唯一方法是使用 sh -c 'eval alias',它什么都不返回,因为它是子壳。

那么有没有办法让子shell的输出回到我开始的进程?请记住,我是通过我创建的进程以编程方式执行此操作的(但这个问题也反映在终端中,同样的事情也会发生)。

我还想评论一些其他的cmets:

是的,我一直在考虑写一个 bash sh 文件并运行它,但我不会遇到同样的问题吗?

简单地运行命令(通过我创建的进程)会导致异常,因为该进程对shell一无所知,所以只运行文件,没有找到内置的,所以失败,这就是为什么 sh -c,或类似的东西,是我唯一的选择。

为什么我需要运行别名?好吧,这只是一个例子,就像我说这是一个内置问题,我使用别名作为例子。但是,就我个人而言,我喜欢 alias,也许我的一些用户也喜欢。

最后一个观察:为什么sh -c 'type alias' 有效?如果sh -c 'command alias' 的原因是因为它被分壳而不返回任何输出,那么为什么type 版本有效?类型特殊?这就是我看到不合理之处的地方。

【问题讨论】:

  • sh -c 打开一个没有定义别名的子shell。这就是为什么它什么都不返回。试试bash -c 'alias "a=blah";alias'
  • 您可以在 bash 脚本中使用 type 命令,然后根据返回字符串以编程方式决定要执行的操作。
  • 内置插件列表变化不大。为什么你认为你需要做实验?为什么它们是否是内置的很重要?只需运行命令——它们应该可以工作。
  • 为什么需要运行alias?任何人都不应该使用别名。
  • “CLI 助手 GUI”不应该使用 shell -- 没有 sh -cbash -c 或其他任何东西 -- 除非你真的知道你是什么正在做。 Shell 注入攻击与 SQL 注入攻击一样严重。如果您的用户在他们的配置中指定了一个文字 argv 数组,那么当且仅当他们真的想要并且有理由需要一个 shell 时,他们才可以指定一个 shell。

标签: bash shell command-line-interface


【解决方案1】:

您在错误的地方寻找一致性,因为您错过了其中一些命令正在执行的关键方面。在不同的上下文中运行相同的命令可能会产生不同的结果。例如,如果您运行 lspwd(不带参数),则结果取决于当前目录。

二分法不在内置命令和非内置命令之间,而是在其行为受哪个 shell 运行它们影响的命令和不受其影响的命令之间。有一个相关性:受运行它们的 shell 影响的大多数命令都是内置的,因为外部命令将无法访问运行它们的 shell 的状态。

  • 命令alias 打印出在当前shell中定义的别名列表。别名是外壳内部状态的一部分。如果您运行一个新的 shell 实例,它开始时没有定义别名,因此alias 打印一个空列表。通常,当您运行交互式 shell 时,您的别名是由您的启动文件定义的别名(例如 ~/.bashrc),这就是 alias 列出的内容。但如果你在命令行上运行aliasunalias,你可以更改那个shell 实例的别名,这不会影响其他shell(试试看,确保你明白发生了什么)。
  • command aliasalias 做同样的事情,因为 alias 是一个内置函数。
  • builtin alias 与 bash 中的 alias 做同样的事情。 builtin 命令是 bash 内置命令。 builtin在其他shell中不存在;在 Ubuntu 上,/bin/sh 不是 bash,而是 dash,一个更小、更快且符合 POSIX 的 shell,但缺少 bash 的一些更高级的功能。这也解释了type builtinbash -c 'type builtin' 会报告 builtin 是一个内置函数。
  • type commandtype type 报告 commandtype 是内置函数,因为它们是 sh 中的内置函数。

您不能从程序中执行内建命令:内建命令是特定 shell 的命令。你可以执行一个支持这个内建的 shell 并告诉它执行那个内建,当然内建是在那个 shell 的上下文中执行的。

你不能从 Pascal 程序执行 alias 命令,就像你不能从 shell 程序调用 Pascal write 函数一样。 shell 内置函数是 shell 的库函数。 Shell 模糊了它们自己的函数和外部程序之间的区别,因为您可以使用相同的语法调用外部程序,而不是通过类似 TProcess 类的东西,但归根结底,概念是相同的。

“CLI 帮助程序 GUI”已经存在:它被称为终端模拟器。听起来您想要制作一些只能执行某些特定命令的更受约束的 GUI。在这种情况下,我认为公开别名等功能是没有意义的。您在这里没有提供到 shell 的接口,而是提供了一个运行程序的接口。您不是在连接外壳,而是在替换它。所以不要想shell 命令,想运行程序。没有名为alias的程序。

【讨论】:

  • 是的!谢谢,我现在对概念很清楚了。在消化了你的精彩解释后,我找到了答案!我会在下面发布。
【解决方案2】:

builtins 直接由当前 shell 执行(因此得名...)。因此,如果您想允许cdalias 之类的命令,或者如果您想允许用户更改环境变量,则必须在自己的shell 中显式地为此编写代码。

唯一的好消息是,一些内置命令仅集成到 shell 中以加快处理速度(直接执行而无需派生新进程),但也作为实际命令存在。尝试在路径中找到它们以确保。

【讨论】:

  • 是的,我见过...例如密码。
【解决方案3】:

当您使用 bash -csh -c 运行某些命令时,一个 new shell process 被分叉。相反,当您获取(使用source somefile. somefile)某个文件时,当前 shell 进程正在读取它(同样for eval)。

任何像alias(或cdulimit)这样的内置命令只会影响当前的shell进程。因此,如果您使用 sh -c 运行它,只有运行它的新 shell 会受到影响,而不是您的父 shell(在您的终端中运行的那个)。

这里没有不合理之处。顺便说一句,您可以尝试 bash -c 'echo $$'echo $$(因为 $$ 给出了当前 shell 的 pid)。

顺便说一句,在(C 或 C++)程序中,您可能会使用 popen(3) 之类的东西。当然,出于类似的原因,在你的 C 程序中使用system(3)system("cd $HOME") 不会像你想要的那样工作(你需要直接调用chdir(2),也许是chdir(getenv("HOME"))

我建议阅读Advanced Linux Programming

还请注意,systempopen 都(隐式)使用/bin/sh -c(并期望符合 POSIX 的 shell),这与 /bin/bash -c 不同。阅读documentation of bash 并注意并非每个sh​​ell 都是interactive shell(特别是system(3)popen(3) 都没有启动交互式shell)。

【讨论】:

  • 感谢您的见解,它们很有帮助。我不知道您是否看到我更新的问题,并想知道您是否对此有任何见解:为什么 > 有效?如果 sh -c 'command alias' 的原因是因为它是子外壳而没有返回输出,那么为什么“类型”版本有效?类型特殊?这就是我看到非理性的地方。
  • 嗯? sh -c 'command alias' 不返回任何输出因为shell 中没有别名(不是“子shell”,这是不同事物的特定术语)。它不会因为成功运行时的输出列表为空而停止成为有效命令。这是一个完全有效的命令,空列表是完全有效的成功输出。如果它没有成功运行,你会在 stderr 上打印一个错误。
  • 想想你是否在运行grep foo /dev/null。输出为空并不意味着grep 没有运行。同样,您的代码在非交互式 shell 中成功运行 alias 命令(因此,它不会获取您的 ~/.bashrc [不是您应该期望以 sh -c 而不是 bash -c 开头的 shell完全是 bash]) 没有定义别名。
  • 好的。你们正式摇滚。现在一切都清楚了。我找到了答案,而且效果很好: bash -i -c 'command alias' 返回输出,yesssss。超级感谢大家。哦,请原谅我可能会混淆术语,我仍然处于 lunux-learn-mode。
  • 我还想对高级 linux 编程的链接表示感谢,它提供了很多信息,我将它推荐给 linux-learn-mode 中的其他人,他们将来会阅读这篇文章。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-19
  • 2012-11-24
  • 1970-01-01
  • 2012-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多