Systemd用户服务中Bash Broken Pipe错误排查与修复
错误现象
终端直接运行脚本正常,但作为systemd用户服务运行时触发Broken Pipe错误,日志如下:
мая 07 10:47:49 nixos systemd[1563]: Starting garmin.service... мая 07 10:47:51 nixos bash[6951]: grep: ошибка записи: Broken pipe мая 07 10:47:51 nixos systemd[1563]: garmin.service: Control process exited, code=exited, status=127/n/a мая 07 10:47:51 nixos systemd[1563]: garmin.service: Failed with result 'exit-code'. мая 07 10:47:51 nixos systemd[1563]: Failed to start garmin.service.
systemd服务配置
[Unit] After=run-media-serg-GARMIN.mount Requires=run-media-serg-GARMIN.mount [Service] Environment="LOCALE_ARCHIVE=/nix/store/ckkhm153z75sw6z5nr277kvn2pi4z8jy-glibc-locales-2.35-224/lib/locale/locale-archive" Environment="PATH=/nix/store/l6jgwxkc3jhr029vfzfwzcy28iyckwsj-coreutils-9.1/bin:/nix/store/gn1s1s5z19cf0wiir2cd38jckcjc6kn6-findutils-4.9.0/bin:/nix/store/pvb117r7fhwb08717ks21a6y9hlnp63b-gnugrep-3.7/bin:/nix/store/v0hg83sdv4v51c0prmdigry6wdmmpzmp-gnused-4.8/bin:/nix/store/34am2kh69ll6q03731imxf21jdbizda2-systemd-251.15/bin:/nix/store/l6jgwxkc3jhr029vfzfwzcy28iyckwsj-coreutils-9.1/sbin:/nix/store/gn1s1s5z19cf0wiir2cd38jckcjc6kn6-findutils-4.9.0/sbin:/nix/store/pvb117r7fhwb08717ks21a6y9hlnp63b-gnugrep-3.7/sbin:/nix/store/v0hg83sdv4v51c0prmdigry6wdmmpzmp-gnused-4.8/sbin:/nix/store/34am2kh69ll6q03731imxf21jdbizda2-systemd-251.15/sbin" Environment="TZDIR=/nix/store/z0kg1c0f8fx6r4rgg5bdy01lb2b9izqg-tzdata-2023a/share/zoneinfo" ExecStart=/nix/store/kga2r02rmyxl14sg96nxbdhifq3rb8lc-bash-5.1-p16/bin/bash -c /nix/store/jqfh2bnzag50wlghwnw52mq2nzr8bzz2-garmin-backup/bin/garmin-backup Type=forking
脚本代码
#!/usr/bin/env bash backupDir="/home/serg/Backup/Garmin Edge 830/" # 为notify-send导出环境变量 export DISPLAY=:0 export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus on_error() { _MSG_=$1 echo "$_MSG_" exit 1 } check_mount() { _MOUNT_=$1 _MSG_=$2 if [ ! -d "$_MOUNT_" ]; then echo "Path to $_MSG_ mount point is not exists" exit 1 fi } writeLog() { _LOG_=$1 _SAVE_PATH_=$2 local outputPath="$_SAVE_PATH_/backup_history_${_LOG_}".log if [ ! -e "$outputPath" ]; then echo "Backup_log: " > "$outputPath" fi date >> "$outputPath" } garmin() { mpointGarmin="$(df -h | grep -i "Garmin" | awk '{print $NF}')" check_mount "${mpointGarmin}" "Garmin" inputDir="${mpointGarmin}/Garmin/" copyArray=( "Activities" "Locations" "Records" "Settings" "Sports" "Totals" "Weight" ) for val in "${copyArray[@]}" do copyStr+=( --include="$val"/*** ) done rsync --dry-run --no-perms --checksum --progress -v -r "${copyStr[@]}" \ --exclude=* "${inputDir}" "${backupDir}" RET_CODE=$? if test $RET_CODE -eq 0; then notify-send "Garmin 830 Backup is Done" fi } garmin
已定位问题
问题出在mpointGarmin="$(df -h | grep -i "Garmin" | awk '{print $NF}')"这一行,移除管道子shell、硬编码挂载点后服务可正常运行。疑问:systemd是否不支持带管道的子shell调用?如何修复?
问题原因与修复方案
systemd完全支持管道和子shell,错误的根源有两个:
1. Type=forking配置错误
你的脚本是前台运行的,没有fork后台进程,但Type=forking会让systemd认为主进程退出后服务就已完成,可能提前关闭标准输出/输入流,导致管道中的grep因为输出端被关闭而触发Broken Pipe错误。
修复:将Type=forking改为Type=simple(或直接删除该行,默认就是simple),同时简化ExecStart(脚本已带shebang,无需通过bash -c调用):
ExecStart=/nix/store/jqfh2bnzag50wlghwnw52mq2nzr8bzz2-garmin-backup/bin/garmin-backup
2. df -h解析挂载点的不可靠性
终端环境与systemd服务环境中,df -h的输出可能存在差异(比如locale编码、设备名称格式),导致grep匹配不到内容,后续check_mount检查空路径失败,间接引发管道错误。
修复方案二选一:
方案A(推荐):直接使用固定挂载点
既然服务已经依赖run-media-serg-GARMIN.mount,挂载点是确定的/run/media/serg/GARMIN,直接硬编码即可:mpointGarmin="/run/media/serg/GARMIN"这是最稳定的方案,避免解析输出的不确定性。
方案B:用
findmnt替代df解析findmnt是专门用于查询挂载信息的工具,输出格式更稳定,可通过标签或UUID精准获取挂载点:# 通过标签获取 mpointGarmin=$(findmnt -n -o TARGET --label GARMIN) # 如果是UUID挂载,替换为: # mpointGarmin=$(findmnt -n -o TARGET --uuid <设备UUID>)
额外优化
保留脚本中的check_mount函数,即使服务依赖挂载单元,也能二次验证挂载点的存在性,提升鲁棒性。
内容的提问来源于stack exchange,提问作者One

