特定 shell 脚本中 `sed` 用法的解释

特定 shell 脚本中 `sed` 用法的解释

在读的时候在线教程,我遇到了以下代码:

#!/bin/bash
# Counting the number of lines in a list of files
# for loop over arguments
# count only those files I am owner of

if [ $# -lt 1 ]
then
  echo "Usage: $0 file ..."
  exit 1
fi

echo "$0 counts the lines of code" 
l=0
n=0
s=0
for f in $*
do
  if [ -O $f ] # checks whether file owner is running the script
  then 
      l=`wc -l $f | sed 's/^\([0-9]*\).*$/\1/'`
      echo "$f: $l"
      n=$[ $n + 1 ]
      s=$[ $s + $l ]
  else
      continue
  fi
done

echo "$n files in total, with $s lines in total"

sed本例中调用的目的是什么?

答案1

示例 6 中的命令sed仅从输出中提取行数wc -l

它正在运行wc -l$f作为参数传入的脚本运行所拥有的文件)。这通常会产生如下输出:

$ wc -l .bashrc
17 .bashrc

第 1 列中的行数和第 2 列中的文件名。该sed命令以一种非常不必要的方式仅获取行数。

$ wc -l .bashrc | sed 's/^\([0-9]*\).*$/\1/'
17

sed声明's/^\([0-9]*\).*$/\1/'执行以下操作:

  • ^- 匹配行的开头
  • \([0-9]*\)- 无限次匹配任何数字(转义括号形成捕获组)
  • .*- 无限次匹配任何东西
  • $- 匹配行尾
  • \1- 表示第一个捕获组的内容。

本质上,这是匹配以数字开头的任何行,并将整行替换为第一个捕获组(数字)。


感谢斯蒂芬·基特推荐这个:

$ wc -l < .bashrc
17

否则使用cutorawk会更好,如下所示:

$ wc -l .bashrc | cut -d' ' -f1
17

$ wc -l .bashrc | awk '{print $1}'
17

答案2

该段代码中的用途sed是解析 的输出以wc -l提取文件中的行数。

这通常是不需要的,因为

l=$( wc -l <"$f" )

会做同样的事情(你应该尝试这个)。


该脚本使用了一些不可移植且被认为“过时”的构造,并且脚本中的一些细节使其不安全。

  1. 扩展应该被引用。例如,if [ $# -lt 1 ]最好写成if [ "$#" -eq 0 ], 并且if [ -O $f ]应该写成if [ -O "$f" ]。这样我们就可以支持包含任何字符的文件名,甚至是其中的字符$IFS(空格、制表符和换行符)。$#如果$IFS出于某种原因包含数字,则应加引号。

    有关此内容的更多信息,请参阅标题为“的其他三个问题”忘记在 bash/POSIX shell 中引用变量的安全隐患“,”为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?“ 和 ”什么时候需要双引号?”。

  2. 在某些情况下,使用反引号进行命令替换很麻烦。该行l=`wc -l ...`可以重写为l=$(wc -l ...).较新的$(...)更好,因为它是嵌套的,因为引用按预期工作(比较例如echo "`echo "`echo ok`"`",它会生成语法错误echo "$(echo "$(echo ok)")"),并且因为它更容易阅读。

    有关这方面的更多信息,请参见“*sh shell 中的反引号(即“cmd”)是否已被弃用?

  3. $[ $s + $l ]是一种不可移植的说法$(( s + l ))

  4. printf变量数据应该使用而不是使用 来输出echo。例如,最后一行,

    echo "$n files in total, with $s lines in total"
    

    可以重写为

    printf '%d files in total, with %d lines in total\n' "$n" "$s"
    

    参见例如“为什么 printf 比 echo 更好?”。

  5. 使用$*循环遍历命令行参数会阻止脚本对包含空格的文件名运行。

  6. 根本不需要该continue语句和else该语句的分支,因为无论如何它都位于循环的最后。if

  7. 诊断输出应打印为标准错误。

脚本的“更正”版本:

#!/bin/bash
# Counting the number of lines in a list of files
# for loop over arguments
# count only those files I am owner of

if [ "$#" -eq 0 ]; then
    printf 'Usage: %s file ...\n' "$0" >&2
    exit 1
fi

printf '%s counts the lines of code\n' "$0"
l=0; n=0; s=0
for name do
    if [ -f "$name" ] && [ -O "$name" ]; then # checks whether its a regular file and file owner is running the script
        nlines=$( wc -l <"$name" )
        printf '%s: %d\n' "$name" "$nlines"
        totlines=$(( totlines + nlines ))
        nfiles=$(( nfiles + 1 ))
    fi
done

printf '%d files in total, with %s lines in total" "$nfiles" "$totlines"

相关内容