有没有一种方法可以指示输入文件行循环中的最后一次迭代?

Sol*_*osa 11 shell-script text-processing

假设我有一个direction包含以下行的文件

east
north
south
west
south-west
Run Code Online (Sandbox Code Playgroud)

并使用循环并echo在 shell 脚本中生成以下输出:

Direction: east
Direction: north
Direction: south
Direction: west
Last direction: south-west
Run Code Online (Sandbox Code Playgroud)

换句话说,我想对脚本中的最后一行做一些不同的事情。

cas*_*cas 22

bash无法检测文件的结尾(不尝试读取下一行并失败),但 perl 可以使用其eof功能:

$ perl -n -e 'print "Last " if eof; print "Direction: $_"' direction 
Direction: east
Direction: north
Direction: south
Direction: west
Last Direction: south-west
Run Code Online (Sandbox Code Playgroud)

注意:与echobash 不同,printperl 中的语句不会打印换行符,除非您 1. 通过包含\n在要打印的字符串中明确告诉它,或者 2. 使用 perl 的-l命令行选项,或者 3. if字符串已经包含换行符......就像$_- 的情况一样,这就是为什么你经常需要chomp()它来删除换行符。

BTW,在 perl 中,$_是当前输入行。或者任何未指定实际变量名称的循环中的默认迭代器(通常称为“当前事物”,可能是因为“dollarunderscore”有点拗口)。如果未提供,许多Perl 函数和运算符将用作默认/隐式参数。$_查看man perlvar并搜索$_.

sed也可以 -$地址与文件的最后一行匹配:

$ sed -e 's/^/Direction: /; $s/^/Last /' direction 
Direction: east
Direction: north
Direction: south
Direction: west
Last Direction: south-west
Run Code Online (Sandbox Code Playgroud)

规则的顺序sed很重要。我的第一次尝试以错误的方式进行了(并打印了“方向:最后的西南方向”)。此 sed 脚本始终将“Direction:”添加到每行的开头。在最后一行 ( $) 中,它将“Last ”添加到已被前一条语句修改的行的开头。

  • 顺便说一句,perl 和 sed 版本之间有一个主要但不明显的区别。如果给定多个输入文件,perl 版本将为每个输入文件的最后一行打印“Last Direction:”。sed 版本只会为整个输入的最后一行打印“Last Direction:”。你可以通过使用 `eof()` 而不仅仅是 `eof` 让 perl 表现得像 sed。原因请参见 `perldoc -f eof`。要让 sed 表现得像 perl 那样,请使用 `-s` 选项 - `sed -s -e '.....'` - 这是 sed 的 GNU 扩展,在其他版本中可能可用,也可能不可用。 (6认同)

ter*_*don 16

循环无法知道何时到达终点,除非它确实到达终点。因此,要么处理文件两次,一次获取行数,另一次输出,或者打印每次迭代时的前一个值和退出循环时的最后一个值:

prev=""
while read direction; do 
    if [ -n "$prev" ]; then 
        echo "Direction: $prev"
    fi
    prev="$direction"
done < direction
echo "Last Direction: $prev" 
Run Code Online (Sandbox Code Playgroud)


Nic*_*teo 10

FelixJN 的答案的一个变体,将行读入数组,但使用 bash 内置函数而不是 bash 循环:

mapfile -t DIR < direction
printf "Direction: %s\n" "${DIR[@]::${#DIR[@]}-1}"
printf "Last direction: %s\n" "${DIR[-1]}"
Run Code Online (Sandbox Code Playgroud)

mapfile是 Bash 4 中添加的内置函数,它将标准输入读取到数组中,每个条目一行。该-t选项会去除换行符。

printf将重复应用其格式字符串,直到用完所有输入参数。

  • 至少从“时间”来看,这似乎是迄今为止最快的。 (2认同)

Fel*_*xJN 7

您可以将值读入数组,然后使用数组长度:

#!/bin/bash
i=0
while read line ; do
  arr[$i]="$line"
  ((i++))
done <file

for (( i=0 ; i<=${#arr[@]}-2 ; i++ )) ; do
  echo Direction: ${arr[i]}
done
echo Last direction: ${arr[-1]}
Run Code Online (Sandbox Code Playgroud)

当然,这意味着所有数据都在 RAM 中。

arr=( $(cat file) )仅当行条目没有空格时,更简单的方法才有效,因此我while read在这里使用了 -loop 。


sch*_*ity 5

您可以混合使用paste,tailhead

$ paste -s -d'\n' <(head -n -1 direction | sed 's/^/Direction: /') <(tail -n1 direction | sed 's/^/Last direction: /')
Direction: east
Direction: north
Direction: south
Direction: west
Last direction: south-west
Run Code Online (Sandbox Code Playgroud)
  • paste -s -d'\n'使用换行符作为分隔符-d'\n',并带有选项 和 选项-s

    -s, --serial
    一次粘贴一个文件而不是并行粘贴

  • head -n -1检索除最后一行之外的所有行,然后通过管道传输到sed仅在这些行中执行您需要的命令。

  • tail -n1 file检索文件的最后一行,然后通过管道传输到sed仅执行该行中所需的命令。


或者更简洁地只是使用cat而不是paste,正如@Mick Matteo 的评论所指出的:

$ cat <(head -n -1 direction | sed 's/^/Direction: /') <(tail -n1 direction | sed 's/^/Last direction: /')
Run Code Online (Sandbox Code Playgroud)

  • 为什么不直接用 `cat` (con**cat**enate) 而不是 `paste -s -d'\n'` 呢? (3认同)