你将如何在bash中实现这一目标。这是我在接受采访时被问到的一个问题,我可以用高级语言来思考答案,但不能在shell中思考答案。
据我所知,tail的真正实现是在文件的末尾寻找,然后向后读。
答案 0 :(得分:9)
主要思想是保持固定大小的缓冲区并记住最后一行。这是使用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
答案 1 :(得分:7)
如果允许所有not-tail命令,为什么不是异想天开?
#!/bin/sh
[ -r "$1" ] && exec < "$1"
tac | head | tac
答案 2 :(得分:4)
使用wc -l
计算文件中的行数。从中减去所需的行数,并添加1,以获取起始行号。然后将其与sed
或awk
一起使用,以开始从该行号打印文件,例如
sed -n "$start,\$p"
答案 3 :(得分:2)
就是这样:
#!/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
传入要查看的文件末尾的行数,然后通过stdin发送文件,将整个文件放入数组中,只打印数组的最后#行。
答案 4 :(得分: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
答案 5 :(得分: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
”,而其他版本则无法打印文字字符串“{{ 1}}“,甚至弄清楚你有哪一个是有点痛苦,特别是如果你没有\n
(其中是在POSIX中),你可能也没有用户定义的函数。
(NB这个答案中的代码,sans基本原理,最初是由用户'Nirk'发布的,但后来在我认为不知道有些shell没有数组的人的低压下被删除了。)
答案 6 :(得分: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