Bash 如何在没有tail命令的情况下编写tail脚本

Bash 如何在没有tail命令的情况下编写tail脚本,bash,unix,Bash,Unix,您将如何在bash中实现这一点。这是我在一次采访中被问到的一个问题,我可以用高级语言想到答案,但不能用shell 据我所知,tail的真正实现是查找文件的结尾,然后向后读取。使用wc-l计算文件中的行数。从中减去所需的行数,再加1,得到起始行号。然后将其与sed或awk一起使用,开始从该行号打印文件,例如 sed -n "$start,\$p" 这是: #!/bin/bash readarray file lines=$(( ${#file[@]} - 1 )) for (( line=$((

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


据我所知,tail的真正实现是查找文件的结尾,然后向后读取。

使用
wc-l
计算文件中的行数。从中减去所需的行数,再加1,得到起始行号。然后将其与
sed
awk
一起使用,开始从该行号打印文件,例如

sed -n "$start,\$p"
这是:

#!/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
#/bin/bash
读取阵列文件
行=$(${文件[@]}-1))
对于((line=$($lines-$1)),i=${1:-$lines};((line<$lines&&i>0));line++,i-);做
echo-ne“${file[$line]}”
完成
根据这一回答:


您在文件末尾传入要查看的行数,然后通过stdin发送文件,将整个文件放入一个数组,并只打印数组的最后几行。

主要思想是保留一个固定大小的缓冲区并记住最后几行。下面是一个使用外壳制作尾部的快速方法:

#!/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
#/bin/bash
尺寸=5
idx=0
读行时
做
arr[$idx]=$line
idx=$((idx+1)%SIZE))
完成<文本

对于((i=0;i在“纯”shell中,我能想到的唯一方法是在将整个文件逐行读取到索引模为n的数组变量中时执行
,其中n是尾行数(默认值10)-即循环缓冲区,然后在读取过程中
结束时,从停止的位置迭代循环缓冲区。从任何意义上讲,它都不是高效或优雅的,但它可以工作并避免将整个文件读取到内存中。例如:

#!/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
!/bin/bash
incmod(){
设i=$1+1
n=2美元
如果[$i-ge$2];则
回声0
其他的
回声$i
fi
}
n=10
i=0
缓冲区=
边读边做
缓冲区[$i]=$line
i=$(incmod$i$n)
已完成<$1
j=$i
echo${buffer[$i]}
i=$(incmod$i$n)
而[$i-ne$j];则
echo${buffer[$i]}
i=$(incmod$i$n)
完成

如果所有非尾部命令都被允许,为什么不做些异想天开的事呢

#!/bin/sh

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

tac | head | tac
!/bin/sh
[-r“$1”]&&exec<“$1”
塔克|头|塔克

如果在采访中有人问我这个问题,我会给出以下答案:

这是什么环境,我有
bash
但没有
tail
?可能是早期启动脚本?我们可以进入其中,这样我们就可以使用完整的shell实用程序吗?或者我们应该看看是否可以压缩一个精简的Perl解释器,即使没有大多数模块,也可以让生活更轻松。你知道吗现在比
bash
小得多,非常适合编写脚本,对吗?这可能也会有帮助。如果没有这两个选项,我们应该检查静态链接的C mini-
tail
需要多少空间,我打赌我可以将它放在与shell脚本相同数量的磁盘块中

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

现在的问题是,在真正可移植的shell中,数组可能不可用(我不知道posixshell规范是否有数组,但肯定有一些遗留Unix shell没有数组)因此,如果您必须仅使用shell内置程序来模拟
tail
,并且它必须在任何地方都能工作,那么这是您所能做到的最好的方法,是的,这很可怕,因为您使用了错误的语言:

#! /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
”,甚至弄清楚您使用的是哪一个也有点麻烦,特别是如果您没有
printf
(POSIX中的),您可能也没有用户定义的函数


(注意:这个答案中的代码,sans-rational,最初是由用户“Nirk”发布的,但后来在投票压力下被删除了,我可以善意地假设这些人不知道某些shell没有数组。)

这个脚本以某种方式模仿了
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
!/bin/bash
shopt-s extglob
长度=10
而[$#-gt 0]];做
案件“$1”
--)
文件+=(“${@:2}”)
打破
;;
-+([0-9]))
长度=${1#-}
;;
-n)
如果[$2!=+([0-9])];然后
echo“指向'-n'的参数无效:$1”
出口1
fi
长度=$2
转移
;;
-*)
echo“未知选项:$1”
出口1
;;
*)
文件+=(“$1”)
;;
以撒
转移
完成
PRINTHEADER=false
中的案例“${#FILES[@]}”
0)
文件=(“/dev/stdin”)
;;
1)
;;
*)
PRINTHEADER=true
;;
以撒
如果=
对于“${!FILES[@]}”中的I;做
F=${FILES[I]}
如果[[$PRINTHEADER==true]];然后
[[I-gt 0]]&回音

echo“==>$F-1这根本不是纯Bash(不过我的似乎是第二个否决票。)AFAIC,标准Unix工具,如
wc
sed
count。纯Bash“实际上从未被使用过。@tripleee:它绝对不是-1。OP从来没有写过
pure bash
要求。@Barmar如果
tail
在面试中不可用,我很确定sed或
> 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