4

你将如何在 bash 中实现这一点。这是我在面试中被问到的一个问题,我可以用高级语言想到答案,但不能用 shell。

据我了解,tail 的真正实现会寻找文件的末尾,然后向后读取。

4

7 回答 7

10

主要思想是保持一个固定大小的缓冲区并记住最后几行。这是使用 shell 进行尾部操作的快速方法:

#!/bin/bash

SIZE=5
idx=0

while read line
do
    arr[$idx]=$line
    idx=$(( ( idx + 1 ) % SIZE )) 
done < text

for ((i=0; i<SIZE; i++))
do
    echo ${arr[$idx]}
    idx=$(( ( idx + 1 ) % SIZE )) 
done
于 2013-09-03T19:47:26.647 回答
7

如果允许所有非尾命令,为什么不异想天开?

#!/bin/sh

[ -r "$1" ] && exec < "$1"

tac | head | tac
于 2013-09-03T19:56:41.293 回答
4

用于wc -l计算文件中的行数。从中减去你想要的行数,然后加 1,得到起始行号。然后将其与sedawk一起使用以从该行号开始打印文件,例如

sed -n "$start,\$p"
于 2013-09-03T19:41:55.053 回答
3

有这个:

#!/bin/bash
readarray file
lines=$(( ${#file[@]} - 1 ))
for (( line=$(($lines-$1)), i=${1:-$lines}; (( line < $lines && i > 0 )); line++, i-- )); do
    echo -ne "${file[$line]}"
done

基于这个答案:https ://stackoverflow.com/a/8020488/851273

您在文件末尾传入要查看的行数,然后通过标准输入发送文件,将整个文件放入数组中,并且只打印数组的最后 # 行。

于 2013-09-03T19:46:52.197 回答
0

该脚本以某种方式模仿tail

#!/bin/bash

shopt -s extglob

LENGTH=10

while [[ $# -gt 0 ]]; do
    case "$1" in
    --)
        FILES+=("${@:2}")
        break
        ;;
    -+([0-9]))
        LENGTH=${1#-}
        ;;
    -n)
        if [[ $2 != +([0-9]) ]]; then
            echo "Invalid argument to '-n': $1"
            exit 1
        fi
        LENGTH=$2
        shift
        ;;
    -*)
        echo "Unknown option: $1"
        exit 1
        ;;
    *)
        FILES+=("$1")
        ;;
    esac
    shift
done

PRINTHEADER=false

case "${#FILES[@]}" in
0)
    FILES=("/dev/stdin")
    ;;
1)
    ;;
*)
    PRINTHEADER=true
    ;;
esac

IFS=

for I in "${!FILES[@]}"; do
    F=${FILES[I]}

    if [[ $PRINTHEADER == true ]]; then
        [[ I -gt 0 ]] && echo
        echo "==> $F <=="
    fi

    if [[ LENGTH -gt 0 ]]; then
        LINES=()
        COUNT=0

        while read -r LINE; do
            LINES[COUNT++ % LENGTH]=$LINE
        done < "$F"

        for (( I = COUNT >= LENGTH ? LENGTH : COUNT; I; --I )); do
            echo "${LINES[--COUNT % LENGTH]}"
        done
    fi
done

示例运行:

> bash script.sh -n 12 <(yes | sed 20q) <(yes | sed 5q)
==> /dev/fd/63 <==
y
y
y
y
y
y
y
y
y
y
y
y

==> /dev/fd/62 <==
y
y
y
y
y
> bash script.sh -4 <(yes | sed 200q)
y
y
y
y
于 2013-09-03T21:44:17.107 回答
0

如果我在面试中被问到这个问题,我会给出以下答案:

bash我有但没有的地方是什么环境tail?可能是早期启动脚本?我们可以busybox进入那里,以便我们可以使用完整的 shell 实用程序吗?或者也许我们应该看看我们是否可以在其中加入一个精简的 Perl 解释器,即使没有大多数可以让生活变得更轻松的模块。你知道dash它比脚本小得多bash而且非常适合脚本使用,对吧?这也可能有帮助。如果这些都不是一个选项,我们应该检查静态链接的 C mini- 需要多少空间tail,我敢打赌,我可以将它放入与您想要的 shell 脚本相同数量的磁盘块中。

如果这不能让面试官相信这是一个愚蠢的问题,那么我继续观察到我不相信使用 bash 扩展,因为现在在 shell 脚本中编写任何复杂的东西的唯一充分理由是,如果总可移植性是压倒一切的关注。通过避免任何不可移植的东西,即使是一次性的,我也不会养成坏习惯,而且我不会想用 shell 做一些事情,而用真正的编程语言来做会更好。

现在的问题是,在真正可移植的 shell 中,数组可能不可用。(我实际上不知道 POSIX shell 规范是否有数组,但肯定有没有它们的遗留 Unix shell。)所以,如果你必须tail只使用 shell 内置函数来模拟并且它必须在任何地方工作,这个是你能做的最好的,是的,这很可怕,因为你用错误的语言写作:

#! /bin/sh

a=""
b=""
c=""
d=""
e=""
f=""

while read x; do
    a="$b"
    b="$c"
    c="$d"
    d="$e"
    e="$f"
    f="$x"
done

printf '%s\n' "$a"
printf '%s\n' "$b"
printf '%s\n' "$c"
printf '%s\n' "$d"
printf '%s\n' "$e"
printf '%s\n' "$f"

调整变量的数量以匹配您要打印的行数。

身经百战的人会注意到这printf也不是 100% 可用的。不幸的是,如果你只有echo,你就会陷入困境:某些版本echo无法打印文字字符串“ -n”,而其他版本则无法打印文字字符串“ \n”,甚至弄清楚你拥有的是哪个版本有点痛苦,特别是,如果您没有printfPOSIX 中),您可能也没有用户定义的函数。

(注意,这个答案中的代码,没有理由,最初是由用户“Nirk”发布的,但后来在人们的反对压力下被删除,我将慈善地假设他们不知道某些 shell 没有数组。)

于 2013-09-03T21:08:55.863 回答
0

在“纯”shell中我能​​想到的唯一方法是while read将整个文件逐行放入一个索引为模n的数组变量中,其中n是尾行数(默认为 10)——即循环缓冲区,然后迭代在结束时从您离开的地方开始循环缓冲区while read。从任何意义上说,它既不高效也不优雅,但它可以工作并且避免将整个文件读入内存。例如:

#!/bin/bash                                                                                 

incmod() {
    let i=$1+1
    n=$2

    if [ $i -ge $2 ]; then
        echo 0
    else
        echo $i
    fi
}

n=10
i=0
buffer=
while read line; do
    buffer[$i]=$line
    i=$(incmod $i $n)
done < $1

j=$i
echo ${buffer[$i]}
i=$(incmod $i $n)
while [ $i -ne $j ]; do
    echo ${buffer[$i]}
    i=$(incmod $i $n)
done
于 2013-09-03T19:48:00.983 回答