当前位置: 首页 > 面试题库 >

需要Linux bash内置exec命令行为的解释

庾君博
2023-03-14
问题内容

从Bash参考手册中,我得到有关execbash内置命令的以下信息:

如果提供了命令,它将替换外壳程序而不创建新进程。

现在,我有以下bash脚本:

#!/bin/bash
exec ls;
echo 123;
exit 0

执行此,我得到了:

cleanup.sh  ex1.bash  file.bash  file.bash~  output.log
(files from the current directory)

现在,如果我有此脚本:

#!/bin/bash
exec ls | cat
echo 123
exit 0

我得到以下输出:

cleanup.sh
ex1.bash
file.bash
file.bash~
output.log
123

我的问题是:

如果在exec调用 它取代了壳,而无需创建一个新的进程 ,为什么放时| cat,将echo 123被打印出来,但是没有它,它不是。因此,如果有人可以解释这种行为的逻辑是什么,我将很高兴。

谢谢。

编辑:@torek响应后,我变得更加难以解释行为:

1. exec ls>outcommand创建out文件并将ls命令结果放入其中;

2. exec ls>out1 ls>out2仅创建文件,但不放入任何结果。如果该命令按照建议的方式工作,则我认为2号命令应具有与1号命令相同的结果(甚至更多,我认为它不应该已经创建了out2文件)。


问题答案:

在这种情况下,您可以exec使用管道。为了执行一系列流水线命令,外壳程序必须首先进行分叉,并创建一个子外壳程序。(具体来说,它必须先创建管道,然后创建派生,以便所有在管道“左侧”运行的东西都可以将其输出发送到管道“右侧”的任何内容。)

要查看实际上这是正在发生的情况,请比较:

{ ls; echo this too; } | cat

与:

{ exec ls; echo this too; } | cat

前者运行时ls不会离开子外壳,因此该子外壳仍然可以运行echo。后者ls通过保留子外壳运行,因此不再在那里做子外壳echo,并且this too不进行打印。

(大括号的使用{ cmd1; cmd2; }通常会抑制您在圆括号中得到的子壳分叉动作(cmd1; cmd2),但是对于管道,该分叉实际上是“强制的”。)

仅当单词后面没有“什么可以运行”时,才进行当前shell的重定向exec。因此,例如exec >stdout 4<input 5>>append修改当前的shell,但是exec foo >stdout 4<input 5>>append尝试执行exec command
foo。[注意:这并非严格准确;见附录。]

有趣的是,在交互式外壳程序中,exec foo >output由于没有命令而失败后foo,外壳程序仍然存在,但stdout仍重定向到file
output。(您可以使用恢复exec >/dev/tty。在脚本中,无法exec foo终止脚本。)

在@ Pumbaa80的提示下,这里的内容更具说明性:

#! /bin/bash
shopt -s execfail
exec ls | cat -E
echo this goes to stdout
echo this goes to stderr 1>&2

(注意:cat -E从我的平时简化了下来cat -vET,这是我方便使用的“让我以可识别的方式查看非打印字符”)。运行此脚本时,来自的输出lscat -E应用(在Linux上,行尾显示为$符号),但是发送到stdout和stderr的输出(在其余两行上) 重定向。更改| cat -E> out,在脚本运行后,观察文件的内容out:最后两个echo不存在。

现在将更ls改为foo(或将找不到的其他命令),然后再次运行脚本。这次的输出是:

$ ./demo.sh
./demo.sh: line 3: exec: foo: not found
this goes to stderr

out现在该文件具有第一echo行生成的内容。

这使得exec“真正做到的” 事情变得尽可能明显(但不再那么明显,因为爱因斯坦没有提出:-))。

通常,当外壳进入执行“简单的命令”(见手册页的精确定义,但此明确排除在“管道”的命令),它准备与指定的任何I /
O重定向操作<>等打开所需的文件。然后,shell调用fork(或一些等效但效率更高的变体,例如vforkclone取决于底层操作系统,配置等),并在子进程中重新排列打开的文件描述符(使用dup2调用或等效方法)以实现所需的最终排列:> out将打开描述符移动到fd 1(stdout),同时6> out将打开描述符移动到fd 6。

exec但是,如果指定关键字,则外壳程序将取消执行该fork步骤。它照常进行所有文件打开和文件描述符重新排列,但是这次, 它影响所有后续命令
。最后,在完成所有重定向后execve(),如果存在命令,shell尝试(从系统调用的角度)尝试命令。如果没有命令,或者execve()调用失败

并且shell应该继续运行(是交互式的或已设置execfail),则shell士兵会继续前进。如果execve()成功,则该外壳不再存在,已被新命令替换。如果execfail未设置,并且外壳不是交互式的,则外壳退出。

(还增加了command_not_found_handleshell函数的复杂性:bash
exec似乎根据测试结果禁止运行它。exec通常,该关键字使shell不会查看其自身的函数,即,如果您有shell函数f,则f作为a
运行简单命令会运行shell函数,就像(f)在子shell中运行壳函数一样,但是运行会(exec f)跳过它。)

至于为什么要ls>out1 ls>out2创建两个文件(带或不带exec),这很简单:shell打开每个重定向,然后使用它们dup2来移动文件描述符。如果您有两个普通的>重定向,则外壳程序将同时打开两个外壳,将第一个重定向到fd
1(stdout),然后将第二个重定向到fd 1(stdout),在此过程中关闭第一个。最终,它运行了ls ls,因为删除了`>out1

out2。只要没有名为ls的文件,该ls`命令就会向stderr投诉,而不会向stdout写入任何内容。



 类似资料:
  • 问题内容: 我已经开始在linux终端中组合不同的命令。我想知道为什么这样的命令需要反斜杠和分号: 当一个简单的cp命令只是: 没有\; 是要清楚地指示命令的结束,还是只是在文档中要求?基本原理是什么? 谢谢! 问题答案: 分号之前反斜杠用于,因为是列表操作符(或之一,),用于分离外壳命令。例如: 该实用程序正在使用或终止由调用的shell命令。 因此,为避免解释特殊的外壳字符,需要使用反斜杠将其

  • outputs/exec 插件的运用也非常简单,如下所示,将 logstash 切割成的内容作为参数传递给命令。这样,在每个事件到达该插件的时候,都会触发这个命令的执行。 output { exec { command => "sendsms.pl \"%{message}\" -t %{user}" } } 需要注意的是。这种方式是每次都重新开始执行一次命令并退

  • 我正在处理我的一个项目。我正在windows机器上使用XAMPP开发该项目。这就是我面临的问题。我需要在服务器上执行shell脚本,并在网页上显示结果。问题是,大多数脚本都按预期运行,但我无法获得以下命令的输出:, ls, cat, pwd 因为这些命令返回给我一个空白数组。 我找不到确切的问题。

  • myStr是上面我要执行的整个命令吗? 我已经尝试过的事情: 给了我一个错误: 它运行起来像一个魅力,但不是通过runtime.exec()。

  • 问题内容: 我试图找出Linux上是否存在程序,并且找到了本文。我尝试从go程序中执行此操作,但它始终提示我无法在$ PATH中找到“命令”,这是预料之中的,因为它是Linux中的内置命令,而不是二进制文件。所以我的问题是如何从go程序中执行linux的内置命令? 错误:执行:“命令”:在$ PATH中找不到可执行文件 问题答案: 就像那篇文章说的那样,“命令”是内置的shell。您可以通过go本

  • 问题内容: 因此,我们有一个作为Windows服务运行的Java进程。它需要使用执行命令。它执行的命令需要UAC。这是在Windows Server 2008上,听起来您无法为单个可执行文件禁用UAC,所以还有其他方法可以使这项工作吗? 问题答案: 如果您的Java应用程序作为Windows服务运行,则它很可能在以下系统帐户之一下运行:SYSTEM(最有可能),LOCAL SERVICE或NETW