我一直在为我父亲编写一个脚本,该脚本通过一个包含许多链接在一起的功能的脚本来管理多项任务(组织目的)。该脚本完全依赖于zenity
所有用户交互(我父亲真的不懂技术{60 岁以上})。
我遇到的问题是,一旦我调用该函数,即使循环所依赖的变量在函数外部发生变化,transprog
它也不会退出循环。while
循环在这里调用:
copystat="1"
transprog & cp -rf "$netlocdirFIN/" "$transdir2/" # "transprog" is called here
copystat="0"
sleep 1
ret="$?"
echo "$ret"
transnew #calls next step/function
这是transprog
函数:
transprog ()
{
i2="Im Working On It!"
(
while [ $copystat = 1 ]; do
# =================================================================
echo "25"
echo "# ^_^ $i2" ; sleep 1
# =================================================================
echo "50"
echo "# ^-^ $i2" ; sleep 1
# =================================================================
echo "75"
echo "# ^_^ $i2" ; sleep 1
# =================================================================
echo "99"
echo "# ^-^ $i2" ; sleep 1
done
echo "# Your files are DONE TRANSFERING!" ; sleep 1
echo "100"
) |
zenity --progress \
--title="File Transfer In Progress" \
--text="# Im Getting Warmed Up" \
--percentage=0 \
--auto-close \
--auto-kill
}
复制完成后,脚本继续运行:zenity进度条在后台滴答作响,这确实让他感到困惑。
我如何告诉后台函数退出?
(zenity 使用echo
25、50、75 和 99 的值来改变进度条的“完整性”。并echo "# (whatever)"
改变文本。)
你只需要告诉他“嘿,达弗斯,我正在复印东西,别打扰我”
我已经尝试过 Guss 的解决方案:(为了测试目的而分离的脚本)
#!/bin/bash
transtest ()
{
i2="Im Working On It!"
var=0
trap 'var=1' 2
(
while [ "$var" = 0 ]; do
# =================================================================
echo "25"
echo "# ^_^ $i2" ; sleep 1
# =================================================================
echo "50"
echo "# ^-^ $i2" ; sleep 1
# =================================================================
echo "75"
echo "# ^_^ $i2" ; sleep 1
# =================================================================
echo "99"
echo "# ^-^ $i2" ; sleep 1
done
echo "# Your files are DONE TRANSFERING!" ; sleep 1
echo "100"
) |
zenity --progress \
--title="File Transfer In Progress" \
--text="# Im Getting Warmed Up" \
--percentage=0 \
--auto-close \
--auto-kill
}
transtest &
pid=$!
echo "$pid"
sleep 20 #code to copy? or just for this test?
echo "Killing Child: $pid"
kill -2 "$pid"
echo "wait"
wait
exit
我看到了结果echo "wait"
并且它仍然在循环。
答案1
您遇到的问题是由调用transprog
在后台运行时生成的子 shell 引起的(&
符号的作用)。您必须了解您的 bash 脚本不是程序,而是按照 shell 规则运行的 shell 脚本 - shell 一次只能运行一个进程并等待它终止。
这 ”工作系统“允许您运行另一个进程而不等待它,但是当该进程是 bash 中定义的函数时,bash 将生成一个“子 shell”,该“副本“原始 shell 的所有变量和函数,并将其作为作业进行跟踪。复制的 shell 不知道其父 shell 所做的更改 - 因为它没有直接引用父 shell 变量 - 只是它们的副本。
您需要为后台循环执行的操作是使用进程间通信机制(工业控制计算机) 让父 shell 通知子 shell 中的循环中止。对于这种简单的信号,通常使用“POSIX 信号”是合适的。
最合适的信号是“中断信号”SIGINT(信号编号 2),这是按下 CTRL+C 时产生的信号,通常会停止脚本。但我们可以使用trap
内置命令捕获此信号并更新本地变体。
这是一个简单的例子:
#!/bin/bash
function test() {
var=0
trap 'var=1' 2
while [ "$var" = 0 ]; do
sleep 3
echo 'still sleeping'
done
echo "done sleeping"
}
test &
pid=$!
echo $pid;
sleep 5
echo "Killing $pid"
kill -2 "$pid"
wait
这将输出:
$ ./test.sh
8359
still sleeping
Killing 8359
still sleeping
done sleeping
这里发生的事情是,5 秒后,循环完成一轮,现在在第二轮中休眠,此时父级使用kill
内置函数向子 shell 发出信号。子 shell 的休眠被中断,它接收信号并更新变量,然后恢复休眠。当循环被唤醒时,它会输出文本,然后返回并看到变量已更改并离开循环。
我使用wait
父进程中的内置函数只是为了确保父进程不会在子 shell 之前退出。它本质上是“等待”子 shell 结束。
[更新] 在进一步查看了 OP 之后,我发现还有几个子 shell 正在运行,我不想反对这一点 - 子 shell(除了函数)是管理脚本复杂性的好方法 - 但这确实使得找到正确的 PID 来将信号发送到少量难的。
相反,我们可以使用另一种称为“共享内存段”的 IPC 机制。这允许一个进程分配一批内存,为其分配一个名称,并让多个进程访问同一内存(上面链接的 IPC 文档对此进行了更详细的介绍)。虽然在 Bash 中我们无法直接实现该功能(例如,与 C 程序可以做到的不同),但我们可以使用 Linux 机制,通过设备/dev/shm
类别将共享内存 IPC 公开给任何程序。因此,OP 脚本可能看起来像这样:
#!/bin/bash
transtest () {
local sync="$1"
i2="Im Working On It!"
var=0
(
while [ -z "$(cat $sync)" ]; do sleep 1; done
echo "# Your files are DONE TRANSFERING!"
sleep 1
echo "100"
) | zenity --progress --title="File Transfer In Progress" --text="# Im Getting Warmed Up" \
--percentage=0 --pulsate --auto-close --auto-kill
}
sync=/dev/shm/syntest-$$ # allocate a shared memory segment name for this process
echo '' > $sync # init the shm
transtest $sync &
sleep 20
echo "Signaling done"
echo "done" > $sync
echo "waiting"
wait
# remove the shm segment. While not actually necessary (it will clear
# when you reboot), I like to keep a clean system
rm $sync
exit
基本上,我们将 SHM 段视为一个文件,并在其中存储一些数据。循环不断读取“文件”,当其中有内容时,就会中断。
还有一个问题,为什么不直接分配一个临时文件(使用mktemp
)并用它来同步呢?好吧 - 效果几乎一样!主要区别在于,这也是我更喜欢使用 SHM 的原因,因为它实际上不是一个文件,不受文件系统 IO 操作、磁盘错误等的影响,如果我无法清理它,它将在系统下次启动时消失。