在读的时候在线教程,我遇到了以下代码:
#!/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
否则使用cut
orawk
会更好,如下所示:
$ wc -l .bashrc | cut -d' ' -f1
17
$ wc -l .bashrc | awk '{print $1}'
17
答案2
该段代码中的用途sed
是解析 的输出以wc -l
提取文件中的行数。
这通常是不需要的,因为
l=$( wc -l <"$f" )
会做同样的事情(你应该尝试这个)。
该脚本使用了一些不可移植且被认为“过时”的构造,并且脚本中的一些细节使其不安全。
扩展应该被引用。例如,
if [ $# -lt 1 ]
最好写成if [ "$#" -eq 0 ]
, 并且if [ -O $f ]
应该写成if [ -O "$f" ]
。这样我们就可以支持包含任何字符的文件名,甚至是其中的字符$IFS
(空格、制表符和换行符)。$#
如果$IFS
出于某种原因包含数字,则应加引号。有关此内容的更多信息,请参阅标题为“的其他三个问题”忘记在 bash/POSIX shell 中引用变量的安全隐患“,”为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?“ 和 ”什么时候需要双引号?”。
在某些情况下,使用反引号进行命令替换很麻烦。该行
l=`wc -l ...`
可以重写为l=$(wc -l ...)
.较新的$(...)
更好,因为它是嵌套的,因为引用按预期工作(比较例如echo "`echo "`echo ok`"`"
,它会生成语法错误echo "$(echo "$(echo ok)")"
),并且因为它更容易阅读。有关这方面的更多信息,请参见“*sh shell 中的反引号(即“cmd”)是否已被弃用?”
$[ $s + $l ]
是一种不可移植的说法$(( s + l ))
。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 更好?”。
使用
$*
循环遍历命令行参数会阻止脚本对包含空格的文件名运行。根本不需要该
continue
语句和else
该语句的分支,因为无论如何它都位于循环的最后。if
诊断输出应打印为标准错误。
脚本的“更正”版本:
#!/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"