返回

grep日志搜索权限问题与退出码处理技巧

Linux

grep 搜索日志文件,权限不足导致退出码异常,如何正确判断?

开发脚本时遇到个麻烦事。脚本用来搜索指定目录下的日志文件,看有没有特定的内容。这个目录下既有普通日志文件,也有 gzip 压缩的日志文件。我的想法是,先搜普通日志,如果没找到,再去搜压缩包里的。有个限制:运行脚本的用户权限不够,读不了目录下的某些文件。

问题来了,grep 就算找到了内容,或者没找到内容,因为有权限问题,都会遇到 "Permission denied",最终退出码总是 2。 就像这样:

[user@mypc ~]$ grep -rnw '/usr/toto/log' -e "pattern"
grep: /usr/toto/log/privileged.log.2025-01-10: Permission denied
grep: /usr/toto/log/privileged.out.1: Permission denied
/usr/toto/log/testontargz.log:1:pattern
grep: /usr/toto/log/privileged.out.3: Permission denied
grep: /usr/toto/log/privileged.log.2025-01-17: Permission denied
grep: /usr/toto/log/privileged.out.2: Permission denied
/usr/toto/log/teston2.log:1:pattern

[user@mypc ~]$ echo $?
2

我晓得 grep -rnw**s** 可以隐藏 "Permission denied" 信息,但退出码该是 2 还是 2,没找到时并不会变成 1,找到也不会变成0。

我试过在 if 里用 $? 判断退出码是不是 1,但现在这情况肯定不行。所以我想,$? 可能不是正解,但我脚本水平有限... 大家给出出主意?

原脚本如下:

#!/bin/sh
printf '\n'

# Search for the error in not compressed log files
printf '%s\n' ">> Errors found in the log file(s):"
grep -rnws '/usr/toto/log' -e "$1"

# Only search in the compressed files if the above grep didn't output any actual result
if [ $? -eq 1 ]; then
    # Search for the error in compressed log files
    printf '\n'
    pattern=$1; shift

    for x do
      if case "$x" in
          *.gz|*.[zZ]) <"$x" gzip -dc | grep -q -e "$pattern";;
         esac
      then
        printf '%s\n' ">> Error found in one of the log files that is stored in the file $x"
      fi
    done
fi

printf '\n'

调用脚本: myscript.sh patterntofind /usr/toto/log/compressedfile.*.gz

一、 问题原因

grep 命令遇到权限问题时,即使找到了匹配项,退出码也会是 2。 这干扰了脚本根据退出码判断是否继续搜索压缩文件的逻辑。$? 获取的是上一个命令的退出码,这里就是 grep 的退出码。而我们的需求是,仅当grep在无权限错误干扰下,未输出任何匹配结果时,其退出码应为 1,才去搜索压缩文件。

二、 解决方案

方案一: 捕获标准输出,结合 grep-q 选项

  1. 原理:

    • grep -q:安静模式。不输出任何内容到标准输出,只返回退出码。 找到匹配项返回 0,没找到返回 1,发生错误(包括权限错误)返回 2。
    • grep 的标准输出重定向到变量,标准错误重定向丢弃。
    • 检查变量是否为空。如果为空,说明没有找到匹配项(即使有权限错误,也不会有匹配项输出到标准输出)。
  2. 代码示例:

#!/bin/sh
printf '\n'

# Search for the error in not compressed log files
printf '%s\n' ">> Errors found in the log file(s):"
output=$(grep -rnw '/usr/toto/log' -e "$1" 2>/dev/null) #将错误输出重定向到 /dev/null

# Only search in the compressed files if the above grep didn't output any actual result
if [ -z "$output" ]; then #检查捕获的输出是否为空
    # Search for the error in compressed log files
    printf '\n'
    pattern=$1; shift

    for x in "$@"; do # 修正:循环遍历剩余参数
      if case "$x" in
          *.gz|*.[zZ]) <"$x" gzip -dc | grep -q -e "$pattern";;
         esac
      then
        printf '%s\n' ">> Error found in one of the log files that is stored in the file $x"
      fi
    done
fi

printf '\n'
  1. 改进说明:
    • 2>/dev/null:把 grep 的标准错误输出重定向到空设备,避免干扰。
    • -z "$output" : 测试output是否为空.
    • for x in "$@": 循环使用更安全的写法。$@ 代表了所有未被shift过的参数列表。

方案二: 两次 grep,一次用于过滤,一次用于确认

  1. 原理:

    • 第一次 grep,不带 -s 选项,将标准输出和标准错误合并 (&>)。
    • 第二次grep 从第一次 grep的输出结果中,再次查找匹配项,并使用-q
    • 判断第二次grep的退出码是否是0.
  2. 代码示例:

#!/bin/sh
printf '\n'

# Search for the error in not compressed log files
printf '%s\n' ">> Errors found in the log file(s):"
first_grep_result=$(grep -rnw '/usr/toto/log' -e "$1")

# Only search in the compressed files if the above grep didn't output any actual result

# 使用管道和grep -q来判断第一次grep是否有真正的结果输出。
if ! echo "$first_grep_result" | grep -q -e "$1"; then
    # Search for the error in compressed log files
    printf '\n'
    pattern=$1; shift

    for x in "$@"; do  # 修正:循环遍历剩余参数
      if case "$x" in
          *.gz|*.[zZ]) <"$x" gzip -dc | grep -q -e "$pattern";;
         esac
      then
        printf '%s\n' ">> Error found in one of the log files that is stored in the file $x"
      fi
    done
fi

printf '\n'
  1. 改进说明:
    • 利用管道和两个grep, 先拿到带错误的结果,再从这些结果中进行二次匹配,如果第二次匹配成功,退出码是0. 如果不成功(仅有权限错误,或真的不匹配),退出码是非0.

方案三 (进阶): 结合find命令规避权限问题 (如果允许更改文件查找方式)

  1. 原理:

    • find 命令可以通过 -readable 测试文件是否可读,从而一开始就避免 grep 访问无权限文件。
    • 只对可读文件执行grep
  2. 代码示例:

#!/bin/sh
printf '\n'

# Search for the error in not compressed log files
printf '%s\n' ">> Errors found in the log file(s):"

# 使用find查找可读文件,并对每个文件执行grep
find /usr/toto/log -type f -readable -print0 | while IFS= read -r -d $'\0' file; do
    grep -Hn -e "$1" "$file"  # 使用-H选项输出文件名
done | grep -q -e "$1"  # 确保至少找到一次才不执行下面的压缩文件搜索

# Only search in the compressed files if no results were found above
 if [ $? -ne 0 ];then
    # Search for the error in compressed log files
    printf '\n'
    pattern=$1; shift

    for x in "$@"; do  #修正: 循环遍历剩余参数
      case "$x" in
          *.gz|*.[zZ])
            if zgrep -q -e "$pattern" "$x"; then # zgrep 简化了压缩文件的搜索
               printf '%s\n' ">> Error found in one of the log files that is stored in the file $x"
            fi
          ;;
      esac
    done
fi

printf '\n'
  1. 改进和安全建议:

    • -print0read -d $'\0':用空字符分隔文件名,更安全地处理包含空格或其他特殊字符的文件名。
    • while IFS= read -r -d $'\0' file循环结构能够安全,完整处理各种文件名
    • grep -Hn-H 选项强制输出文件名,即使只搜索一个文件。
    • 使用 zgrep:直接搜索压缩文件,更简洁。
    • find命令更精细, 可以指定-type f表示查找文件,-readable控制查找可读文件.
    • 最终的grep -q检查可以确认是否find + grep真的有结果. 如果$? -ne 0, 表示没结果。

方案三是最稳妥的, 通过find控制可读文件范围, 直接避免了grep触发权限错误, 后续处理更简单和准确.