如何使 bash 内置的“读取”忽略注释或空行?

如何使 bash 内置的“读取”忽略注释或空行?

(为简单起见,我假设要读取的文件是第一个参数 - $1。)

我可以做我想做的事外在地和:

tempfile=$(mktemp)
awk '/^#/ {next}; NF == 0 {next}; {print}' "$1" > $tempfile
while read var1 var2 var3 var4 < $tempfile; do
  # stuff with var1, etc.
done

awk然而,每次解析配置文件时都需要调用似乎很荒谬。有没有办法让read忽略文件中的注释行或仅空白行,没有外部二进制文件/潜在的性能问题?


到目前为止的答案非常有帮助!澄清一下,我不想使用临时文件,但我想要读取配置从文件中,不是来自标准输入。我很清楚,在调用脚本时可以使用输入重定向,但由于各种原因,这在我的情况下不起作用。

我想对要读取的输入进行软编码,例如:

configfile="/opt/myconfigfile.txt"
[ $# -gt 0 ] && [ -r "$1" ] && configfile="$1"

while read var1 var2 var3 var4 < "$configfile" ; do
  ...

configfile但是当我尝试这个时,它只是一遍又一遍地读取第一行,直到我终止该进程。

也许这应该是它自己的问题......但这可能是我正在做的事情的一行变化。我的错误在哪里?

答案1

您不需要临时文件来执行此操作,并且 sed(或 awk)在注释处理方面比 shell case 语句灵活得多。

例如:

configfile='/opt/myconfigfile.txt'
[ $# -gt 0 ] && [ -r "$1" ] && configfile="$1"

sed -e 's/[[:space:]]*#.*// ; /^[[:space:]]*$/d' "$configfile" |
    while read var1 var2 var3 var4; do
      # stuff with var1, etc.
    done

# Note: var1 etc are not available to the script at this
# point. They are only available in the sub-shell running
# the while loop, and go away when that sub-shell ends.

这会删除注释(带或不带前导空格)并从输入中删除空行,然后再将其输送到 while 循环中。它单独处理行上的注释以及附加到行尾的注释:

# full-line comment
# var1 var2 var3 var4
abc 123 xyz def # comment here

像这样的呼叫sedawk任务并不“荒谬”,而是完全正常的。这就是这些工具的用途。至于性能,我敢打赌,除了非常小的输入文件之外,该sed版本会快得多。管道传输sed有一些启动开销,但运行速度非常快,而 shell 则很慢。


2022年5月3日更新:

请注意,当 while 循环结束时,在 while read 循环中设置的变量(var1、var2、var3 等)将“超出范围”。只能在 while 循环内部使用。 while 循环正在子 shell 中运行,因为配置文件通过管道传输到其中。当该子 shell 死亡时,它的环境和子进程也会随之消失不能改变其父进程的环境。

如果您希望变量在 while 循环后保留其值,则需要避免使用管道。例如,使用输入重定向 ( <) 和流程替代( <(...)):

while read var1 var2 var3 var4; do
  # stuff with var1, etc.
done < <(sed -e 's/[[:space:]]*#.*// ; /^[[:space:]]*$/d' "$configfile")

# remainder of script can use var1 etc if and as needed.

使用此进程替换版本,while 循环在父 shell 中运行,并且sed脚本作为子进程运行(其输出重定向到 while 循环)。 sed 及其环境在完成后消失,而运行 while 循环的 shell 保留循环创建/更改的变量。

答案2

这是有效的,因为read会破坏空白 (IFS) 上的所有内容,因此如果 var1 为空或以“#”开头,则跳过它。

while read var1 var2 var3 var4; do
   case $var1 in
       ''|\#*) continue ;;         # skip blank lines and lines starting with #
   esac
   echo "var1: '$var1'"
   # stuff with var1, etc.
done < "${1:-default_config_file}"

然后输入必须重定向到循环而不是while命令列表。如果不为空则扩展"${1:-default_config_file}"为第一个命令行参数,否则扩展为default_config_file您也可以在默认值字符串中使用变量扩展等。

因为您对最小化预处理感兴趣,所以我认为这是等效的,但也删除了所有注释:

while read line; do
    echo "${line%%#*}" | {
        read var1 var2 var3 var4
        [ -z "$var1" ] && continue
        # stuff with var1, etc.
        for i in 1 2 3 4; do eval echo "\"var$i: \$var$i\""; done  #debug only!
    }
done < "${1:-default_config_file}"

这使用了 shell 参数扩展子字符串处理功能。 扩展为除第一个和删除后的所有内容之外${line%%#*}的原始值。将其加载并照常继续。测试缩短了,因为我们现在只需要检查空字符串,而不是。line#var1-4continue#

答案3

您可以在不创建临时文件的情况下执行此操作。 grep 命令将过滤空行和注释行。

while read var1 var2 var3; do
    echo $var1
    echo $var2
    echo $var3
    echo "etc..."
done < <(grep -v "^#\|^$" /opt/myconfigfile.txt)

相关内容