我需要一个可以执行以下操作的 bash 脚本:
- 使用 wget 并行下载 3 个文件,它们不必同时完成。
- 可以同时取消 3 个下载
CTRL+C
(已经完成的下载除外)。
目前,我wget -c URL
在各自的终端上运行 3 个并行实例。但只想有一个等待用户输入取消的脚本,这样我就可以在 Linux 重新启动后继续。
我的系统是带有 Cygwin 的 Windows。
答案1
您可以使用 GNUparallel
来实现这一点。将 URL 放入数组中,然后wget
运行parallel
:
#!/bin/bash
urls=( "https://example.com" "https://example1.com" "https://example2.com")
parallel wget -c ::: "${urls[@]}"
Parallel 将并行运行命令,并停止脚本(例如使用Ctrl+C将终止并行,终止所有三个正在运行的 wget 实例,同时不影响任何已完成的实例。
您应该能够parallel
在操作系统的存储库中找到。
如果您不想或无法安装parallel
,您也可以在 bash 中本地执行此操作:
#!/bin/bash
trap 'kill $(jobs -p)' EXIT
urls=( "https://example.com" "https://example1.com" "https://example2.com" )
for url in "${urls[@]}"; do
wget -c "$url" &
done
wait
这里的技巧是使用trap
内置函数(参见help trap
bash)来陷阱kill $(jobs -p)
EXIT 信号的命令。jobs -p
将返回当前 shell 的所有子进程的 PID,因此在本例中它将是正在运行的wget
进程的 PID。这些被传递到kill
杀死它们的地方。因此,当您终止脚本时,它会传递给其子脚本。确保wait
脚本将等到所有后台作业完成运行后再退出,以便您仍然可以wget
使用Ctrl+终止脚本,进而终止进程C。
答案2
Bash 脚本位于这个另一个答案需要陷阱,因为 Bash 在单独的进程组中运行命令(包括异步命令)。终端知道前台进程组并在Ctrl+上向该组发送 SIGINT c。当 Bash 脚本wait
位于前台时,它bash
本身位于前台进程组中,但不是wget
它所生成的。 SIGINT(如果有)到达bash
,而不是wget
s。这就是为什么您需要上述答案中的陷阱。
其他 shell 的工作方式可能有所不同。在我的 Kubuntu 中,posh
不会创建新的进程组,它会运行它所属的进程组中的所有内容。这一事实使我们能够简化脚本:
#!/usr/bin/posh
set -- 'https://example.com' 'https://example1.com' 'https://example2.com'
for url do
wget -c "$url" &
done
wait
如果当我们的脚本在前台运行时按Ctrl+ ,终端将直接发送 SIGINT 到cposh
和到wget
s 因为它们将位于与前台进程组相同的进程组中。
笔记:
posh
不支持数组,所以我将 URL 保存为位置参数。您可以删除该set …
行;然后在调用脚本时指定 URL(例如./the_script 'https://example.com' 'https://example1.com' 'https://example2.com'
)。通过这种方式,您可以将脚本变成并行下载任意内容的工具。- 另一个答案
kill
在陷阱中使用,默认信号是 SIGTERM,它与 SIGINT 不同(尽管wget
可能以相同的方式做出反应)。您可以发送kill
SIGINT 或其他任何信号,这是您的选择(kill -s …
)。在我们的脚本中我们无法选择。我们不传递任何信号,我们不生成任何信号。我们首先设置好一切,因此在Ctrl+c时,终端不仅向解释脚本的 shell 发送 SIGINT,还向wget
s 发送。
另一个答案很好,GNUparallel
是正确的工具,Bash 应该trap
可以工作;所以从某种意义上说我的回答是不必要的。我仍然想向您展示,了解Ctrl+ 的c工作原理以及不同 shell 的工作原理,您只需选择不同的 shell 即可获得所需的行为。