我发现很难准确地表达这个问题,但我会尽力而为。我用作dwm
默认窗口管理器和dmenu
应用程序启动器。除了浏览器之外,我几乎不使用 GUI 应用程序。我的大部分工作都是直接从命令行完成的。此外,我非常喜欢操作系统、应用程序等方面的极简主义。我从未摆脱过的工具之一是应用程序启动器。主要是因为我对应用程序启动器如何工作/它们做什么缺乏准确的理解。即使广泛的互联网搜索也只能显示模糊的解释。我想做的是甚至摆脱我的应用程序启动器,因为除了实际生成应用程序之外,我绝对没有用它。为了做到这一点,我真的很想知道如何“正确”地从 shell 启动应用程序。其中“正确”的含义可以近似为“就像应用程序启动器会做的那样”。我并不声称所有应用程序启动器都以相同的方式工作,因为我对它们了解不够。
我知道以下从 shell 生成进程的方法:
exec /path/to/Program
用指定命令替换shell而不创建新进程sh -c /path/to/Program
启动 shell 相关进程/path/to/Program
启动 shell 相关进程/path/to/Program 2>&1 &
启动 shell 独立进程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
通过 启动dmenu
,firefox
使用 启动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
?
答案1
有几种方法可以执行程序并将其与终端分离。一种是在后台运行一个子shell的,像这样(替换firefox
为您最喜欢的程序):
(firefox &)
另一种是否认这个过程:
firefox & disown firefox
如果您对应用程序启动器的工作原理感到好奇,请分别dmenu
提供 1 个二进制文件和 2 个 shell 脚本:dmenu
、dmenu_path
和dmenu_run
。
dmenu_run
将 的输出通过管道传输dmenu_path
到 dmenu,而 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 中运行。另一种无需通过管道传输到 shell 的编写方法dmenu_run
是:
#!/bin/sh
$(dmenu_path | dmenu "$@") &
答案2
我非常喜欢G-Man的回答。但我之所以做出回应,是因为我认为你的担忧令人困惑。正如韦恩指出的,最好的答案是“无论什么都能得到你想要的结果”。
在Unix进程管理中,每个进程都有一个父进程。唯一的例外是init
操作系统在启动时启动的进程。父进程在死亡时带走所有子进程是正常行为。这是通过向所有子进程发送 SIGHUP 信号来完成的; SIGHUP 的默认处理会终止进程。
用户进程的 shell 生成与您编写的代码没有什么不同叉子(2)/执行(3)以您选择的语言进行通话。 shell 是您的父进程,如果 shell 终止(例如,您注销),那么它生成的子进程也会随之消失。您描述的细微差别只是修改该行为的方法。
exec /path/to/program
就像打电话执行(3)。是的,它会将您的 shell 替换为program
,保留任何父级启动的 shell。
sh -c /path/to/program
有点毫无意义地创建一个子 shell 进程,该进程将创建program
.仅当/path/to/program
它实际上是一系列脚本指令而不是可执行文件时才有价值。 (sh /path/to/script.sh
可用于在劣质 shell 中运行缺乏执行权限的 shell 脚本)
/path/to/program
创建一个“前台”进程,这意味着 shell 会等待该进程完成,然后再执行任何其他操作。在系统调用上下文中,它就像叉子(2)/执行(3)/等待进程(2)。请注意,子进程从父进程继承 stdin/stdout/stderr。
/path/to/program &
(忽略重定向)创建一个“后台进程”。该进程仍然是 shell 的子进程,但父进程并不等待它终止。
nohup /path/to/program
调用诺哈普(1)program
如果控制终端关闭,则阻止发送 SIGHUP 。无论是在前台还是后台都是一个选择(尽管最常见的是该进程是后台的)。请注意,如果您不以其他方式重定向标准输出,则这nohup.out
只是输出。
当您将进程置于后台时,如果父进程终止,则会发生以下两种情况之一。如果父母是控制终端,然后 SIGHUP 将发送给孩子们。如果不是,则该进程可能是“孤立的”,并由该init
进程继承。
当您重定向输入/输出/错误时,您只需将每个进程拥有的文件描述符连接到与从其父进程继承的文件不同的文件。这些都不会影响进程所有权或树深度(但将所有 3 个进程从终端重定向到后台进程总是有意义的)。
话虽如此,我认为您不应该关心 shell 或子 shell 或子进程的进程创建,除非您正在解决与进程管理相关的特定问题。
答案3
嗯,看来你对此有很好的理解。为了澄清你所拥有的一些内容,
sh -c /path/to/Program
非常类似于$嘘 %/路径/到/程序 %Ctrl+D (或者你可以输入“出口”) $
在其中启动新的 shell 进程,提供新 shell 的应用程序命令路径,然后让新 shell 终止。为了便于说明,我已经展示了新的 shell 给出了不同的提示;这在现实生活中可能不会发生。该构造对于执行棘手的操作最有用,例如将多个命令包装到一个包中,使它们看起来像单个命令(类似于一次性未命名脚本),或者可能从 shell 变量构建复杂的命令。您几乎不会仅使用它来运行具有简单参数的单个程序。
sh -c "command"
2>&1
表示将标准错误重定向到标准输出。这实际上并没有太大关系&
;相反,当命令将错误消息发送到屏幕时,即使您说 并且想要捕获文件中的错误消息,也可以使用它。command > file
- 将输出重定向到
nohup.out
是 的一个微不足道的副作用nohup
。的主要目的 是异步运行(通常称为“在后台”,或者用你的话说,作为“独立于 shell 的进程”)并配置它,以便它有更好的机会能够继续运行,如果您当命令仍在运行时终止 shell(例如,注销)。nohup command &
command