检查输入是否是有效的 shell 命令,Linux
Checking if input is a valid shell command, Linux
我正在为作业开发一个简单的 shell。我读取用户输入的命令,将其标记化,fork()
,然后在子进程中使用 execvp()
在后台执行命令。
问题是我需要实现记录有效命令的历史功能。我知道检查用户输入的字符串是否是有效命令的唯一方法是检查 execvp()
returns -1。这是检查无效命令的好方法,但由于对 execvp()
的调用发生在子进程中,并且我用于历史记录的数据结构被复制到 fork()
上的子进程而不是共享,我无法使用子项中 execvp()
的结果更新历史记录(因为历史结构是一个副本,我所做的任何更改都不会反映在父结构的副本中)。
有什么方法可以检查 execvp()
是否会 return -1 而无需实际调用它(即在分叉之前或之后)?如果我能找到一种方法来做到这一点,我就能够在父进程中验证 execvp()
是否会成功,并使用该信息正确更新我的历史数据结构。
您要求的是一个系统调用,它可以让您实现经典的先检查后竞争条件。
在该错误中,程序会验证某个操作是否可行,然后执行该操作,从而可能会在检查后立即发生某些外部事件,从而使该操作非法。
所以即使程序检查了它是可能的,操作也会失败。这通常会导致混乱。
您应该避免这种反模式,系统 API 应该会有所帮助,不会用您只会让自己陷入麻烦的系统调用来引诱您。在这种情况下,系统做了正确的事情;没有这样的 API.
父进程最终必须检索子进程的退出状态。那是您需要更新(或不更新)历史记录的时刻。如果失败的 execvp 导致子级 exit() 并显示失败状态代码,父级将注意到失败并且可以通过不将命令行添加到历史记录来做出反应。
经过一番思考后添加的一些注释:
要获取子进程的状态码,父进程会调用wait
或waitpid
。对于同步执行,父级可能会立即执行;对于异步执行,父级将在收到 SIGCHLD
信号时执行此操作。但是父进程必须这样做,以避免僵尸进程。
在异步执行的情况下,不能使用这种策略来避免将无效的命令放入历史,因为异步命令在启动时必须记录在历史中。出于类似的原因,Posix shells 也将命令的异步执行视为成功,即使该命令无效。
虽然这个练习无疑具有教学价值(正如我希望这个答案所证明的那样),但它实际上是一种处理 shell 历史的糟糕方法。虽然 shell 用户偶尔会使用历史记录来检索和重新执行成功的命令,但历史记录功能对于检索和编辑不成功的命令更有用。无法从历史功能中进行更正非常烦人。 (许多 Android 应用程序恰好在搜索历史记录中表现出这个恼人的缺陷:在搜索结果不理想后,您可以检索不正确的搜索并重新运行它,但不能修改它。我很高兴地说事情已经自从我第一次 Android.)
我正在为作业开发一个简单的 shell。我读取用户输入的命令,将其标记化,fork()
,然后在子进程中使用 execvp()
在后台执行命令。
问题是我需要实现记录有效命令的历史功能。我知道检查用户输入的字符串是否是有效命令的唯一方法是检查 execvp()
returns -1。这是检查无效命令的好方法,但由于对 execvp()
的调用发生在子进程中,并且我用于历史记录的数据结构被复制到 fork()
上的子进程而不是共享,我无法使用子项中 execvp()
的结果更新历史记录(因为历史结构是一个副本,我所做的任何更改都不会反映在父结构的副本中)。
有什么方法可以检查 execvp()
是否会 return -1 而无需实际调用它(即在分叉之前或之后)?如果我能找到一种方法来做到这一点,我就能够在父进程中验证 execvp()
是否会成功,并使用该信息正确更新我的历史数据结构。
您要求的是一个系统调用,它可以让您实现经典的先检查后竞争条件。
在该错误中,程序会验证某个操作是否可行,然后执行该操作,从而可能会在检查后立即发生某些外部事件,从而使该操作非法。
所以即使程序检查了它是可能的,操作也会失败。这通常会导致混乱。
您应该避免这种反模式,系统 API 应该会有所帮助,不会用您只会让自己陷入麻烦的系统调用来引诱您。在这种情况下,系统做了正确的事情;没有这样的 API.
父进程最终必须检索子进程的退出状态。那是您需要更新(或不更新)历史记录的时刻。如果失败的 execvp 导致子级 exit() 并显示失败状态代码,父级将注意到失败并且可以通过不将命令行添加到历史记录来做出反应。
经过一番思考后添加的一些注释:
要获取子进程的状态码,父进程会调用
wait
或waitpid
。对于同步执行,父级可能会立即执行;对于异步执行,父级将在收到SIGCHLD
信号时执行此操作。但是父进程必须这样做,以避免僵尸进程。在异步执行的情况下,不能使用这种策略来避免将无效的命令放入历史,因为异步命令在启动时必须记录在历史中。出于类似的原因,Posix shells 也将命令的异步执行视为成功,即使该命令无效。
虽然这个练习无疑具有教学价值(正如我希望这个答案所证明的那样),但它实际上是一种处理 shell 历史的糟糕方法。虽然 shell 用户偶尔会使用历史记录来检索和重新执行成功的命令,但历史记录功能对于检索和编辑不成功的命令更有用。无法从历史功能中进行更正非常烦人。 (许多 Android 应用程序恰好在搜索历史记录中表现出这个恼人的缺陷:在搜索结果不理想后,您可以检索不正确的搜索并重新运行它,但不能修改它。我很高兴地说事情已经自从我第一次 Android.)