You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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配合信号触发的中断机制,既保证等待精度,又能让信号立即打断等待流程,避免轮询浪费资源。

关键实现逻辑

  1. 在信号处理函数末尾添加中断当前sleep的指令,让信号触发后立即终止等待;
  2. 编写高精度的可中断等待函数,每次最多sleep 0.5秒平衡响应速度与资源占用;
  3. 适配浮点数类型的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 03:12:35