Arch Linux+i3环境下可被信号中断的Bash幻灯片定时器问题
Bash脚本实现可中断的定时器(适配i3壁纸幻灯片)
问题背景
在Arch Linux+i3环境下编写了一款每5秒自动切换壁纸的Bash幻灯片脚本,已绑定启停、上下切换、加速减速的快捷键,还通过父脚本实现了「终止其他同类型脚本+启动对应目录幻灯片」的功能,用于快速切换不同壁纸集。
现在需要在主循环末尾添加一个可被信号中断的$timer时长等待命令,但遇到以下问题:
sleep无法被信号直接中断,使用短间隔循环sleep会失去实时响应的意义;timeout在终端可正常执行,但脚本中哪怕执行空命令:都提示权限不足;read -t $timer在终端启动时工作正常,但通过i3快捷键等方式启动时失效,尝试子shell读取、read << /dev/null等方法均无效,似乎读取到空集合而非无输入。
可行方案:结合sleep与信号陷阱实现实时响应
核心思路是用sleep配合信号触发的中断机制,既保证等待精度,又能让信号立即打断等待流程,避免轮询浪费资源。
关键实现逻辑
- 在信号处理函数末尾添加中断当前sleep的指令,让信号触发后立即终止等待;
- 编写高精度的可中断等待函数,每次最多sleep 0.5秒平衡响应速度与资源占用;
- 适配浮点数类型的
timer变量,保证加速/减速后的时间计算精度。
修改后的完整代码
#!/bin/bash notify-send "Started Slideshow" -t 3000 timer=5.0 # 初始化为浮点数,适配小数计算 # 切换壁纸函数 change_background() { feh --bg-center --bg-fill --no-fehbg "$1" } default_image_dir="Cool Pictures" # 信号处理函数 signal_handler() { case $1 in "next") printf "Received next signal\n\n" next_image=true ;; "previous") printf "Received previous signal\n\n" prev_image=true ;; "toggle") notify-send "Slideshow $(if $slideshow_playing; then 'Paused'; else 'Resumed'; fi)" -t 2000 slideshow_playing=!$slideshow_playing printf "Received toggle signal\nSlideshow playing? $slideshow_playing \n\n" ;; "faster") printf "Received faster signal\n\n" timer=$(echo "$timer / 1.1" | bc -l) printf "New timer: %.2f seconds\n" "$timer" ;; "slower") printf "Received slower signal\n\n" timer=$(echo "$timer * 1.1" | bc -l) printf "New timer: %.2f seconds\n" "$timer" ;; *) echo "Unknown signal: $1" ;; esac # 发送SIGINT打断当前sleep,实现实时响应 kill -s SIGINT $$ 2>/dev/null } # 可被中断的等待函数 interruptible_wait() { local target_timer=$1 local start_time=$(date +%s.%N) local elapsed=0 while (( $(echo "$elapsed < $target_timer" | bc -l) )); do # 计算剩余等待时间,最多sleep 0.5秒 local remaining=$(echo "$target_timer - $elapsed" | bc -l) local sleep_time=$(echo "if ($remaining > 0.5) 0.5 else $remaining" | bc -l) # sleep会被信号打断,打断后直接退出循环 sleep "$sleep_time" # 更新已用时间 elapsed=$(echo "$(date +%s.%N) - $start_time" | bc -l) done } # 设置信号陷阱 trap 'signal_handler "next"' SIGRTMIN+1 trap 'signal_handler "previous"' SIGRTMIN+2 trap 'signal_handler "toggle"' SIGRTMIN+3 trap 'signal_handler "faster"' SIGRTMIN+4 trap 'signal_handler "slower"' SIGRTMIN+5 # 目录参数处理 resized_dir=$2 true_dir="$HOME/Pictures/Desktop Background Slideshow/${1:-$default_image_dir}/$resized_dir" printf "Reading images from: $true_dir \n\n" # 打乱图片数组 shuffle_images() { mapfile -d '' shuffled_images < <(find "$true_dir" -type f -print0 | shuf -z) total_images=${#shuffled_images[@]} } shuffle_images i=0 slideshow_playing=true next_image=false prev_image=false single_play=false # 主循环 while true; do if $next_image; then next_image=false single_play=true ((i++)) ((i >= total_images)) && i=0 elif $prev_image; then printf "Previous image, current index: $i\n" prev_image=false single_play=true ((i--)) ((i < 0)) && i=$((total_images - 1)) elif! $slideshow_playing; then # 暂停状态下仅做短间隔等待,减少资源占用 interruptible_wait 0.5 continue else ((i++)) ((i >= total_images)) && i=0 fi if $slideshow_playing || $single_play; then image="${shuffled_images[$i]}" [[ "$3" == "none" ]] && echo "Setting background to: $image" [[ -z "$2" ]] && printf "Changing to: $image \n\n" change_background "$image" single_play=false fi # 仅在播放状态下执行定时等待 $slideshow_playing && interruptible_wait "$timer" done
核心修改点说明
- 信号实时响应:在
signal_handler末尾添加kill -s SIGINT $$,直接打断当前sleep操作,让脚本立即处理信号; - 高精度等待:
interruptible_wait函数用纳秒级时间计算,每次最多sleep 0.5秒,既保证响应速度,又避免频繁轮询; - 变量类型优化:将
timer初始化为浮点数,适配bc的小数运算,保证加速/减速后的时间精度; - 暂停逻辑优化:暂停状态下仅做0.5秒间隔等待,减少系统资源消耗;
- 用户体验提升:优化
toggle信号的通知内容,让用户直观看到幻灯片状态变化。
内容的提问来源于stack exchange,提问作者Self learning student
相关产品推荐
相关产品推荐

