如何从外壳“正确”启动应用程序


21

我很难精确地表达问题,但我会尽力而为。我使用dwm默认的窗口管理器dmenu作为我的应用程序启动器。除了浏览器外,我几乎不使用GUI应用程序。我的大部分工作都是直接从命令行完成的。此外,我非常热衷于有关操作系统,应用程序等的极简主义。我从未遗弃的工具之一是应用程序启动器。主要是因为我对应用程序启动器的工作方式/工作方式缺乏确切的了解。即使是广泛的互联网搜索也只能显示模糊的解释。我想做的就是摆脱我的应用程序启动器,因为除了实际生成应用程序之外,我对此完全没有用。为了做到这一点,我真的很想知道如何从外壳“正确”启动应用程序。因此,“正确”的含义可以近似为“像应用程序启动程序那样”。

我知道以下几种从shell生成进程的方法:

  1. exec /path/to/Program 用指定的命令替换shell而不创建新进程
  2. sh -c /path/to/Program 启动依赖于外壳的进程
  3. /path/to/Program 启动依赖于外壳的进程
  4. /path/to/Program 2>&1 & 启动外壳独立进程
  5. nohup /path/to/Program & 启动独立于Shell的进程并将输出重定向到 nohup.out

更新1:我可以举例说明在不同条件下dmenu从重复调用到重新构造它的方法ps -efl。它产生一个新的shell,/bin/bash并作为该shell的子级应用程序/path/to/Program。只要孩子在身边,壳就会在附近。(如何管理它超出了我的范围...)相反,如果您nohup /path/to/Program &从shell 发出,/bin/bash则该程序将成为该shell的子级,但如果退出该shell,则该程序的父级将是最高级的进程。因此,如果第一个进程例如是/sbin/init verbose并且已经存在,PPID 1那么它将成为程序的父级。这里就是我试图用一个图表来解释:chromium推出通过dmenufirefox使用推出exec firefox & exit

systemd-+-acpid
        |-bash---chromium-+-chrome-sandbox---chromium-+-chrome-sandbox---nacl_helper
        |                 |                           `-chromium---5*[chromium-+-{Chrome_ChildIOT}]
        |                 |                                                    |-{Compositor}]
        |                 |                                                    |-{HTMLParserThrea}]
        |                 |                                                    |-{OptimizingCompi}]
        |                 |                                                    `-3*[{v8:SweeperThrea}]]
        |                 |-chromium
        |                 |-chromium-+-chromium
        |                 |          |-{Chrome_ChildIOT}
        |                 |          `-{Watchdog}
        |                 |-{AudioThread}
        |                 |-3*[{BrowserBlocking}]
        |                 |-{BrowserWatchdog}
        |                 |-5*[{CachePoolWorker}]
        |                 |-{Chrome_CacheThr}
        |                 |-{Chrome_DBThread}
        |                 |-{Chrome_FileThre}
        |                 |-{Chrome_FileUser}
        |                 |-{Chrome_HistoryT}
        |                 |-{Chrome_IOThread}
        |                 |-{Chrome_ProcessL}
        |                 |-{Chrome_SafeBrow}
        |                 |-{CrShutdownDetec}
        |                 |-{IndexedDB}
        |                 |-{LevelDBEnv}
        |                 |-{NSS SSL ThreadW}
        |                 |-{NetworkChangeNo}
        |                 |-2*[{Proxy resolver}]
        |                 |-{WorkerPool/1201}
        |                 |-{WorkerPool/2059}
        |                 |-{WorkerPool/2579}
        |                 |-{WorkerPool/2590}
        |                 |-{WorkerPool/2592}
        |                 |-{WorkerPool/2608}
        |                 |-{WorkerPool/2973}
        |                 |-{WorkerPool/2974}
        |                 |-{chromium}
        |                 |-{extension_crash}
        |                 |-{gpu-process_cra}
        |                 |-{handle-watcher-}
        |                 |-{inotify_reader}
        |                 |-{ppapi_crash_upl}
        |                 `-{renderer_crash_}
        |-2*[dbus-daemon]
        |-dbus-launch
        |-dhcpcd
        |-firefox-+-4*[{Analysis Helper}]
        |         |-{Cache I/O}
        |         |-{Cache2 I/O}
        |         |-{Cert Verify}
        |         |-3*[{DOM Worker}]
        |         |-{Gecko_IOThread}
        |         |-{HTML5 Parser}
        |         |-{Hang Monitor}
        |         |-{Image Scaler}
        |         |-{JS GC Helper}
        |         |-{JS Watchdog}
        |         |-{Proxy R~olution}
        |         |-{Socket Thread}
        |         |-{Timer}
        |         |-{URL Classifier}
        |         |-{gmain}
        |         |-{localStorage DB}
        |         |-{mozStorage #1}
        |         |-{mozStorage #2}
        |         |-{mozStorage #3}
        |         |-{mozStorage #4}
        |         `-{mozStorage #5}
        |-gpg-agent
        |-login---bash---startx---xinit-+-Xorg.bin-+-xf86-video-inte
        |                               |          `-{Xorg.bin}
        |                               `-dwm-+-dwmstatus
        |                                     `-xterm---bash-+-bash
        |                                                    `-pstree
        |-systemd---(sd-pam)
        |-systemd-journal
        |-systemd-logind
        |-systemd-udevd
        |-wpa_actiond
        `-wpa_supplicant

更新2:我想这个问题也可以归结为:流程的父级应该是什么?例如,它应该是一个外壳,还是应该是一个init过程,即具有的过程PID 1


3
对于您的问题的简短回答是“无论什么都能得到您想要的结果”。
韦恩·沃纳2014年

1
当当,垃圾-您问了一些好问题。但我认为韦恩(Wayne)在这里-您最近的编辑问init-答案可能是...也许?这取决于您打算如何/是否与之交谈,init您使用什么以及数据通道在哪里。通常,这些东西会自行解决-这就是要这样做的init原因。在任何情况下,通常当您守护进程then时init。或者,如果要作业控制,请使用当前的shell。
mikeserv

哈哈哈,@ mikeserv欢呼;早上凌晨4:37,这已经是当天的第一场笑声。没错,这些东西总会以某种方式解决。我将删除dmenu并查看如何与所学内容相处。我发现exec /path/to/Program & exit/bin/bash -c /path/to/Program & exit相当有用。但是,他们都作出1init在母公司Program这是我没意见,只要这是有道理的,并没有违反任何基本*nix原则。
lord.garbage 2014年

@ lord.garbage- exec &我想那是因为你。我通常只是在终端上做我的东西...也许您会从Ben crowell的问题中得到一些帮助。我在那里有一个答案,但是所有人都很好。无论如何,当您为某个进程提供背景并且其父级死亡时:sh -c 'cat & kill $$'您将其孤立,最终收成。那是init的工作-这就是为什么他们都屈服于此的原因。
mikeserv

也许现在一个更简单的问题是:如何从shell中获取上述进程树:systemd--bash--chromiumsystemd--chromium当我从外壳中生成Firefox时,我尝试的所有方法最终都将导致以下形式的进程树。贝壳在这里如何被妖魔化?它与任何终端都没有关联。
lord.garbage 2014年

Answers:


7

好吧,您似乎对此有很好的理解。为了弄清楚您所拥有的一些,

  • sh -c /path/to/Program 相当类似于

    $ SH/路径/到/节目Ctrl+ D                             (或者你可以键入“ 退出”) 
    $

    在此处启动新的Shell进程,提供新Shell的应用程序命令路径,然后让新Shell终止。我已经展示了新的shell,它给出了不同的提示以供说明。在现实生活中这可能不会发生。该结构对于执行棘手的事情最有用,例如将多个命令包装到一个包中,使它们看起来像一个命令(某种一次性使用的未命名脚本),或者可能从shell变量中构建复杂的命令。您几乎不会仅仅将其用于运行带有简单参数的单个程序。sh -c "command"

  • 2>&1表示将标准错误重定向到标准输出。这并没有多大关系&; 而是在命令将错误消息发送到屏幕时使用它,即使您说了 并且想要捕获文件中的错误消息。command > file
  • 将输出重定向到nohup.out的微不足道的副作用nohup。的主要目的 是异步运行(俗称“后台”或“独立于外壳的进程”,以使用您的语言)并对其进行配置,这样,如果您有机会继续运行,则更有可能在命令仍在运行时终止外壳程序(例如,注销)。nohup command &command

bash(1)Bash参考手册 是很好的信息来源。


7

有几种方法可以执行程序并将其与终端分离。一种方法是在subshel​​l的后台运行它,如下所示(firefox用您喜欢的程序替换):

(firefox &)

另一个是放弃该过程:

firefox & disown firefox

如果您想了解应用程序启动器是如何工作的,dmenu提供1个二进制和2个shell脚本:dmenudmenu_pathdmenu_run分别。

dmenu_run通过管道将dmenu_pathdmenu 的输出传递给dmenu,然后将其通过管道传递给$SHELL变量设置为的内容。如果为空,将使用/bin/sh

#!/bin/sh
dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &

dmenu_path稍微复杂一点,但是简而言之,它提供了$PATH环境变量中的二进制文件列表,并在可能的情况下使用缓存。

#!/bin/sh
cachedir=${XDG_CACHE_HOME:-"$HOME/.cache"}
if [ -d "$cachedir" ]; then
        cache=$cachedir/dmenu_run
else
        cache=$HOME/.dmenu_cache # if no xdg dir, fall back to dotfile in ~
fi
IFS=:
if stest -dqr -n "$cache" $PATH; then
        stest -flx $PATH | sort -u | tee "$cache"
else
        cat "$cache"
fi

没有必要在shell中运行程序。dmenu_run不用管道输入外壳的另一种写法是:

#!/bin/sh
$(dmenu_path | dmenu "$@") &

6

我非常喜欢G-Man的答案。但是我之所以做出回应是因为我认为您正在使疑惑困惑。正如韦恩(Wayne)指出的那样,最好的答案是“无论什么都能得到想要的结果”。

在Unix进程管理中,每个进程都有一个父进程。一个例外是init操作系统在启动时启动的过程。父进程在死亡时将其所有子进程都带走是正常的行为。这是通过将SIGHUP信号发送给所有子进程来完成的。SIGHUP的默认处理将终止该过程。

用户进程的外壳生成与您使用所选语言编写的fork(2) / exec(3)调用相同。该外壳程序是您的父级,如果外壳程序终止(例如,您注销),则它产生的子进程将随之而来。您描述的细微差别只是修改该行为的方法。

exec /path/to/program就像调用exec(3)一样。是的,它将用替换您的shell program,并保留所有启动该shell的父对象。

sh -c /path/to/program一种无意义的创建一个子外壳进程,该子外壳进程将创建一个子进程program。仅当/path/to/program实际上是脚本指令序列而不是可执行文件时,它才有价值。(sh /path/to/script.sh可用于运行劣质shell中缺少执行权限的shell脚本)

/path/to/program创建一个“前台”进程,这意味着外壳程序在执行任何其他操作之前会等待该进程完成。在系统调用上下文中,就像fork(2) / exec(3) / waitpid(2)一样。请注意,子级从父级继承stdin / stdout / stderr。

/path/to/program &(忽略重定向)将创建一个“后台进程”。该进程仍然是shell的子进程,但父进程并不等待其终止。

nohup /path/to/program如果控制终端关闭,则调用nohup(1)防止将SIGHUP发送到program。选择是在前台还是在后台(尽管最常见的是后台进程)。请注意,nohup.out仅当您不重定向标准输出时才输出。

当您将一个流程放在后台时,如果父流程死亡,则会发生以下两种情况之一。如果父母是控制终端,则将SIGHUP发送给孩子。如果不是,则该过程可能是“孤立的”,并由该init过程继承。

当您重定向输入/输出/错误时,您只需将每个进程具有的文件描述符连接到与其从父进程继承的文件不同的文件。这都不影响进程所有权或树的深度(但是将所有3个进程都从终端重定向到后台进程总是有意义的)。

综上所述,除非您要解决与流程管理有关的特定问题,否则我不认为您应该关心Shell或子Shell或子流程创建流程。


nitpick:sh -c /path/to/program如果缺少可执行位,则不会将程序作为shell脚本运行sh /path/to/programsh -c /path/to/program只会打开一个shell并/path/to/program在该shell中作为命令运行,如果它不是可执行文件,则会失败。
filbranden

好吧,如果我们挑剔,我们都是错的。sh -c /path/to/program从中读取命令/path/to/program作为输入到外壳程序。它不需要文件具有执行权限,但它应该是一个shell脚本
jwm

嗯,sh /path/to/program做到了。刚刚尝试了一下自己:echo echo hello world >abc.sh,然后sh ./abc.sh打印hello world,同时sh -c ./abc.shsh: ./abc.sh: Permission denied(与您./abc.sh直接在当前shell中运行相同)。我错过了什么吗?(或者也许我在之前的评论中表现不佳...)
filbranden

我的错。除了生成下层外壳程序外,与在命令提示符下sh -c _something_键入内容相同_something_。因此,如果丢失执行位(作为文件),它将失败,这是正确的。另一方面,您可以提供一系列类似的shell命令sh -c "echo hello world",它将正常工作。因此,它不需要您键入的内容具有执行位(甚至是文件),只需要shell解释程序可以执行该操作即可。
jwm

我认为OP指的是某些编译的或系统可执行文件,因此假定具有执行权限。
jwm
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.