bashでこれをどのように達成しますか。これは私がインタビューで尋ねられた質問で、高級言語では答えを思いつくことができましたが、シェルでは思いつきませんでした。
私が理解しているように、実際の tail の実装はファイルの最後までシークしてから逆方向に読み取ります。
テール以外のすべてのコマンドが許可されている場合、気まぐれにならないのはなぜですか?
#!/bin/sh
[ -r "$1" ] && exec < "$1"
tac | head | tac
wc -l
ファイル内の行数をカウントするために使用します。これから必要な行数を引き、1 を足して開始行番号を取得します。次に、これをsed
またはとともに使用しawk
て、その行番号からファイルの印刷を開始します。
sed -n "$start,\$p"
「純粋な」シェルで考えることができる唯一の方法は、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
実際に面接でこの質問をされた場合、私は次のように答えます。
bash
私が持っているが、持っていない環境は何tail
ですか?おそらく、初期の起動スクリプトですか?busybox
シェル ユーティリティの完全な補完を使用できるようにそこに入ることができますか? あるいは、人生をずっと楽にするモジュールのほとんどがなくても、必要最小限の Perl インタープリターを詰め込むことができるかどうかを確認する必要があるかもしれません。dash
よりもはるかに小さく、bash
スクリプトでの使用に最適ですよね? それも役立つかもしれません。それができない場合は、静的にリンクされた C mini がどのくらいのスペースを必要とするかを確認するtail
必要があります。必要なシェル スクリプトと同じ数のディスク ブロックに収まるはずです。
それがばかげた質問だとインタビュアーに納得させられない場合、私はbash拡張機能を使用することを信じていないことを観察します.最優先の懸念。一時的なものであっても移植できないものを避けることで、悪い習慣を身につけることはありません。実際のプログラミング言語で行うほうがよい場合でも、シェルで何かを行う誘惑に駆られることはありません。
問題は、真にポータブルなシェルでは、配列が利用できない場合があるということです。(POSIXシェル仕様に配列があるかどうかは実際にはわかりませんが、それらを持たないレガシーUnixシェルが確かに存在します。)したがって、シェル組み込みのみを使用してエミュレートする必要があり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にある)持っていない場合は、おそらくユーザー定義関数も持っていないためです。
(注:この回答のコードは、理論的根拠がなく、もともとユーザー「Nirk」によって投稿されましたが、一部のシェルには配列がないことに気付いていなかったと私が慈善的に想定する人々からの反対票の圧力の下で削除されました。)
このスクリプトはどういうわけか模倣し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