一文搞定shell文件读取的所有用法

本节内容摘要

本文会通过示例进行介绍文件读取的各种应用场景和代码案例,帮你快速了解shell的各种文件读取用法和学会最佳实践。

按行读取文件

按行读取文件代码示例:

while IFS= read -r line; do




    printf '%s\n' "$line"
done < "$file"


读取时进行简单处理

-r​ 选项用于 read​ 命令,可以防止反斜杠的解释(通常用作反斜杠换行符号,以便在多行上继续或转义分隔符)。如果不使用此选项,输入中的任何未转义的反斜杠都将被丢弃。在使用 read​ 命令时,几乎总是应该使用 -r​ 选项。

最常见的例外是在使用 -e​ 选项时,该选项使用 Readline 从交互式 shell 中获取行。在这种情况下,制表符补全将添加反斜杠来转义空格等字符,而您不希望它们被字面包含在变量中。但是,这种情况永远不会用于逐行读取任何内容,因此在这种情况下仍应始终使用 -r​ 选项。

默认情况下,read 命令会修改每一行的内容,即删除所有前导和尾随空格字符(如果存在于 IFS 中,则包括空格和制表符)。如果不需要这样的行为,可以清除 IFS​ 变量,就像上面的示例一样。如果需要将行首和行尾的空格删除,则可以不清除 IFS​ 变量。

# Leading/trailing whitespace trimming.
while read -r line; do

  printf '%s\n' "$line"

done < "$file"

如果您想要操作每一行中的各个字段,可以向 read​ 命令提供额外的变量。

# 输入包含三列,通过空格或者tab进行分割
while read -r first_name last_name phone; do
  # 打印第二列
  printf '%s\n' "$last_name"
done < "$file"

如果使用了特殊的分割符,可以通过IFS进行设置

# 从 /etc/passwd提取用户名
while IFS=: read -r user pass uid gid gecos home shell; do
  printf '%s: %s\n' "$user" "$shell"
done < /etc/passwd

对于制表符分隔的文件,请使用 IFS=\t​,但要注意,输入中的多个制表符字符将被视为一个分隔符(而Ksh93/ZshIFS=‘\t’​,但要注意,输入中的多个制表符字符将被视为一个分隔符(而 Ksh93/Zsh 的 IFS=‘\t\t’​ 解决方法在 Bash 中无法工作)。

您不一定需要知道每行输入包含多少个字段。如果提供的变量数量超过字段数,则多余的变量将为空。如果提供的变量数少于字段数,则最后一个变量将获得在前面的变量都被满足后剩余的所有字段。例如,

# Bash



read -r first last junk <<< 'Bob Smith 123 Main Street Elk Grove Iowa 123-555-6789'
# first = "Bob", last = "Smith".
# junk = 123 Main Street Elk Grove Iowa 123-555-6789

有些人使用下划线_​ 作为“占位符变量”来忽略字段。它也可以在单个 read​ 命令中使用多次:

# Bash



read -r _ _ first middle last _ <<< "$record"

# 跳过前两个字段读取后续的.
# 下划线可以忽略多个

需要注意的是,只有在 Bash 中使用 _​ 才能保证能够正常工作。许多其他 shell 使用 _​ 来表示其他用途,这可能会导致该脚本失效。因此最好选择一个在脚本中没有其他用途的唯一变量,尽管 _​ 是 Bash 中的常见约定。

如果期望屏蔽掉#​开头的注释

# Bash



while read -r line; do

  [[ $line = #* ]] && continue
  printf '%s\n' "$line"


done < "$file"

输入块

重定向 < “file​告诉while​循环从变量file”​ 告诉 while​ 循环从变量 file​ 中保存的文件名中读取。如果您希望使用文本路径名而不是变量,也可以这样做。如果您的输入源是脚本的标准输入,则根本不需要进行重定向。

如果您的输入源是一个变量/参数的内容,Bash 可以使用 here string​ 迭代其行:

while IFS= read -r line; do




  printf '%s\n' "$line"





done <<< "$var"

在 Shell 脚本中,Heredocs 和 Herestrings 可以用来方便地将文本传递给命令或者将文本写入文件。

Heredocs 是一种用于将多行文本传递给命令或脚本的方法。使用 Heredocs 可以将文本嵌入到 Shell 脚本中,而无需将文本存储在外部文件中。Heredocs 使用 <<​ 运算符,后跟一个自定义的标识符来标识文本块的开头。然后在接下来的行中输入文本,直到行中包含该标识符,并且不希望将其包含在文本中为止。例如:

cat <<END This is a Heredoc example. It allows you to embed multiple lines of text without having to escape any characters. END

在上面的示例中,<<END​ 表示 Heredocs 的开始,END​ 是自定义的标识符。然后在接下来的行中,输入要嵌入的文本,直到再次输入 END​ 为止。在此示例中,cat​ 命令将输出 Heredocs 中的文本。

Herestrings 是一种将文本传递给命令或脚本的方法,类似于 Heredocs。但是,它们使用单行文本,而不是多行文本,并且不需要自定义标识符。Herestrings 使用 <<<​ 运算符,后跟一个字符串来传递文本。例如:

grep foo <<< “This is a line of text containing the word foo.”

在上面的示例中,<<<​ 运算符将字符串 “This is a line of text containing the word foo.”​ 传递给 grep​ 命令。grep​ 命令将输出包含字符串 foo​ 的行。

Herestrings 和 Heredocs 都是非常有用的 Shell 编程工具,可以大大简化 Shell 脚本中的文本处理。

在任何 Bourne 类型的 shell 中都可以使用“here document”来执行相同的操作(尽管 read -r​ 是 POSIX 标准,而不是 Bourne shell)。

while IFS= read -r line; do




  printf '%s\n' "$line"





done <<EOF
$var
EOF

除了从普通文件中读取外,还可以从命令中读取:

some command | while IFS= read -r line; do
  printf '%s\n' "$line"





done

配合find命令进行定制化处理在日常工作中非常有用

find . -type f -print0 | while IFS= read -r -d '' file; do
    mv "$file" "${file// /_}"
done

这个例子从 find​ 命令中逐个读取文件名,并将文件重命名,将空格替换为下划线。

请注意在 find​ 命令中使用 -print0​,它使用 NUL 字节作为文件名分隔符;以及在 read​ 命令中使用 -d ”​,以指示它将所有文本读入文件变量,直到找到 NUL 字节为止。默认情况下,find​ 和 read​ 命令将输入分隔符设置为换行符;但是,由于文件名本身可能包含换行符,此默认行为将在换行符处拆分文件名,并导致循环体失败。此外,还需要将 IFS​ 设置为空字符串,否则 read​ 命令仍会删除前导和尾随空格

find​ 命令是一个非常强大的文件查找工具,可以在指定的目录中递归查找文件和目录。find​ 命令的 -print​ 选项用于输出找到的文件和目录的名称,每个名称占一行。

有时候,文件名可能包含空格或其他特殊字符,这会导致一些问题。例如,如果一个文件名包含空格,则默认情况下 find​ 命令会将文件名分成多个行,这会导致脚本无法

处理该文件名。为了解决这个问题,可以使用 -print0​ 选项,它使用 NUL 字符作为文件名分隔符,而不是使用换行符。

当使用 -print0​ 选项时,find​ 命令会将每个文件名之间用 NUL 字符分隔,而不是用换行符分隔。这样,Shell 脚本就可以正确处理包含空格和其他特殊字符的文件名了。在 Shell 脚本中,可以使用 read​ 命令的 -d​ 选项指定 NUL 字符作为分隔符,以读取 find​ 命令输出的文件名。

例如,下面的命令可以递归查找当前目录下的所有文件和目录,并将它们的名称输出到屏幕上,每个名称之间用 NUL 字符分隔:

find . -print0

如果要将 find​ 命令的输出传递到另一个命令中,以便进一步处理,可以使用管道和 xargs 命令。xargs 命令可以读取 find​ 命令的输出,并将其作为参数传递给另一个命令。例如,下面的命令可以查找当前目录下的所有文本文件,并将它们的名称传递给 grep 命令:

find . -type f -name “*.txt” -print0 | xargs -0 grep “pattern”

在这个命令中,find​ 命令查找当前目录下的所有 .txt​ 文件,并使用 -print0​ 选项输出它们的名称。管道操作符将 find​ 命令的输出传递给 xargs 命令,它使用 -0​ 选项读取输入,并将每个文件名作为参数传递给 grep 命令。

使用管道将 find​ 命令的输出发送到 while​ 循环中会将循环放置在子 Shell 中,这意味着您进行的任何状态更改(更改变量、cd、打开和关闭文件等)都将在循环结束时丢失。为避免这种情况,可以使用进程替换:

while IFS= read -r line; do




  printf '%s\n' "$line"





done < <(some command)

非正常文件处理方式

如果文件的最后一行后面还有一些字符(或者换句话说,如果最后一行没有以换行符终止),那么 read​ 命令将读取该行内容,但会返回 false,同时将未完成的部分行留在 read​ 变量中。您可以在循环后处理这些行:

while IFS= read -r line; do




  printf '%s\n' "$line"





done < "$file"


[[ -n $line ]] && printf %s "$line"

或者


# 这个不工作:
printf 'line 1\ntruncated line 2' | while read -r line; do echo $line; done


# 这个也会异常:
printf 'line 1\ntruncated line 2' | while read -r line; do echo "$line"; done; [[ $line ]] && echo -n "$line"

# 这个处理方式符合预期:
printf 'line 1\ntruncated line 2' | { while read -r line; do echo "$line"; done; [[ $line ]] && echo "$line"; }

第一个示例除了缺少循环后的测试外,还缺少引号。有关原因,请参见“引号或参数”页面的说明。

关于为什么上面的第二个示例不按预期工作的讨论,请参考前面章节说明。

或者,您可以在 while 测试中添加逻辑 OR 操作符:

while IFS= read -r line || [[ -n $line ]]; do
  printf '%s\n' "$line"





done < "$file"




printf 'line 1\ntruncated line 2' | while read -r line || [[ -n $line ]]; do echo "$line"; done

如何防止其他命令“吃掉”输入

有些命令会贪婪地读取标准输入中的所有数据。上面的示例没有采取措施来防止这种情况发生。例如,

while read -r line; do
  cat > ignoredfile
  printf '%s\n' "$line"

done < "$file"

只会打印第一行的内容,其余内容会被写入“ignoredfile”,因为 cat​ 命令会贪婪地读取所有可用的输入内容。

一种解决方案是使用一个数字文件描述符,而不是标准输入:

# Bash



while IFS= read -r -u 9 line; do
  cat > ignoredfile

  printf '%s\n' "$line"


done 9< "$file"

# 注意:read -u并非所有shell都支持
while IFS= read -r line <&9; do
  cat > ignoredfile
  printf '%s\n' "$line"
done 9< "$file"

或者:

exec 9< "$file"
while IFS= read -r line <&9; do
  cat > ignoredfile

  printf '%s\n' "$line"


done
exec 9<&-

这个示例将等待用户在每次循环迭代时向文件 ignoredfile​ 中输入内容,而不是贪婪地读取循环输入。

例如,您可能需要这种解决方案,当使用 mencoder 时,如果有用户输入,它将接受输入,但如果没有,它将继续执行而不发出任何警告。其他表现类似的命令包括 ssh 和 ffmpeg。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYxBZYML' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片