2013-09-03 2 views
3

Как бы вы это достигли в bash. Это вопрос, который меня задали в интервью, и я мог придумать ответы на языках высокого уровня, но не в оболочке.Как написать скрипт хвоста без команды хвоста

Как я понимаю, реальная реализация хвоста стремится к концу файла, а затем читается в обратном направлении.

+2

«в Баш» «в оболочке» не вы имеете в виду какие-либо внешние команды нет t разрешено использовать? например sed/awk/wc? – Kent

+0

@ t0mmyt Эта реальная реализация без использования буфера и просто поиск невозможна с помощью stdin или pipe. Что касается чтения назад, я не уверен в этом, но все возможно, если вы уже используете массивы. Пожалуйста, проверьте мое решение, написанное в чистом баше. Я думаю, что более чем достаточно, чтобы доказать эту концепцию. – konsolebox

ответ

8

Основная идея - сохранить буфер фиксированного размера и запомнить последние строки. Вот быстрый способ сделать хвост с помощью оболочки:

#!/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 
+0

Я думаю, что это нужно немного исправить: если вы используете «текст» из 6 строк, например, последняя строка будет обернута как первая строка на выходе. –

+0

@MannyD У меня, наверное, больше ошибок – cnicutar

+0

@MannyD Я исправил это (но представил уродливый багизм :-(). Спасибо за указание. – cnicutar

4

Используйте wc -l, чтобы подсчитать количество строк в файле. Вычтите количество строк, которые вы хотите от этого, и добавьте 1, чтобы получить номер стартовой строки. Затем используйте это с sed или awk, чтобы начать печать файла с этого номера строки, например.

sed -n "$start,\$p" 
+2

Почему молчаливый голос? – Barmar

+1

-1 Это совсем не чисто Баш. (Однако Mine является вторым downvote.) – tripleee

+0

AFAIC, стандартные инструменты Unix, такие как 'wc' и' sed' count. «Чистый баш» практически никогда не используется. – Barmar

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

Вы передаете в количестве строк в конце файла, который вы хотите видеть затем отправить файл с помощью стандартного ввода , помещает весь файл в массив и выводит только последние # строки массива.

+0

Вы отмените удаление части ответа, который печатает их в обратном порядке. – Barmar

+0

@JonLin Не печатает ли это в обратном порядке? – konsolebox

+1

@konsolebox хм, вы правы, исправлены. –

0

Единственный способ, которым я могу думать в «чистой» оболочки является сделать while read построчно на весь файл в переменную массива с индексации по модулю п, где 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 
7

Если разрешены все команды без хвоста, почему бы не быть причудливым?

#!/bin/sh 

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

tac | head | tac 
+4

'exit $?' Является избыточным - статус завершения по умолчанию оболочки - это команда последнего запуска. –

+0

Вы правы, конечно. Из долгой привычки, как правило, у меня есть сценарии, которые говорят «выход», когда они выходят за дверь, и это необязательно. – sjnarv

0

Вот ответ, который я дал бы, если бы я на самом деле задал этот вопрос в интервью:

В какой среде это где я bash но не tail? Возможно, ранние сценарии загрузки? Можем ли мы получить busybox, чтобы мы могли использовать полный набор утилит оболочки? Или, может быть, мы должны увидеть, можем ли мы сжать урезанный интерпретатор Perl, даже без большинства модулей, которые облегчили бы жизнь. Вы знаете, dash намного меньше, чем bash и отлично подходит для использования в скриптах, верно? Это также может помочь. Если ни одна из них не является опцией, мы должны проверить, сколько пространства потребуется для статической связи C mini- tail, я готов поспорить, что могу поместиться в том же количестве блоков диска, что и требуемый сценарий оболочки.

Если это не убедить интервьюер, что это глупый вопрос, то я иду на заметить, что я не верю в использовании расширений Баша, потому что только хорошая причина, чтобы написать что-нибудь сложное в сценарии оболочки в настоящее время если общая переносимость является главной проблемой. Избегая чего-либо, что не переносится даже в одноразовых случаях, у меня не развиваются вредные привычки, и У меня не возникает соблазна сделать что-то в оболочке, когда это будет лучше сделано на реальном языке программирования.

Теперь, в действительно портативной оболочке, массивы могут быть недоступны. (Я действительно не знаю, имеет ли спецификация оболочки 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), у вас, вероятно, также нет пользовательских функций.

(NB Код в этом ответе, без обоснования, был первоначально размещен пользователем «NIRK», но затем удаляется под давлением downvote от людей, которых я милосердно предполагающих не было известно, что некоторые оболочки не имеют массивы.)

+0

Нет необходимости в благотворительной интерпретации - вопрос явно помечен 'bash'. –

+0

@CharlesDuffy Но это глупо. Если у вас есть «bash», тогда нет никакой правдоподобной ситуации, когда у вас также нет «хвоста». (На самом деле, я думаю, что весь вопрос глупо. Я * действительно видел реальную ситуацию, когда «кошка» недоступна - очень ранние сценарии загрузки в Solaris 2.5, которые ставили почти все в '/ usr' - и это был одним из ужасно ограниченных оболочек, о которых я думаю. Но в настоящее время нет причин не помещать 'busybox' в такую ​​среду, предоставляя вам полный набор утилит.) – zwol

+0

Это вопрос интервью (и явно описывается как такой фронт). Это не должно иметь смысла в реальном мире. –

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 
Смежные вопросы