В этом скрипте всего несколько мелких ошибок, и я бы изменил несколько стилистических вещей. Давайте построчно пройдемся по оригиналу:
#!/bin/bash
port=$1
Типичный #!
и простое присваивание, у нас хорошее начало.
found=$(cat /etc/services | grep -o '[[:space:]]$port/' | wc -l)
В этой строке есть небольшая проблема, которую можно не заметить на первый взгляд. Поскольку $port
находится в сильных кавычках, '…$port'
, он не будет расширяться до своего значения. Вы можете использовать слабые кавычки вместо "…$port"
. Кроме того, здесь вам не нужен кошка
. grep
принимает имя файла в качестве аргумента, но если это не так, вы всегда можете использовать перенаправление (grep 'шаблон' < /etc/services
). Кроме того, grep -c
записывает количество совпадающих строк, поэтому wc -l
здесь также избыточен.
while [ $found -ge 1 ]; do
$port=$($port+1)
done
Цикл кажется почти в порядке. Вы захотите указать сущности в тесте на случай, если $found
может оказаться пустым. Кроме того, вы назначаете с помощью port=
, а не $port=
(но вы использовали правильную форму выше, поэтому я предполагаю, что это была просто опечатка). Наконец, $($port+1)
означает (например, если порт
равен 22
) «Вывод команды 22+1
".Очевидно, вам нужен «Результат арифметического выражения 22+1
», который очень похож на $(($port+1))
.
echo "found: $found"
echo "port: $port"
С этими строками все в порядке.
Теперь, если вы сделаете все вышеперечисленные изменения, чтобы программа стала синтаксически правильной, она все равно не будет делать то, что вы хотите. Вы не указали «правильный» вывод, поэтому я предполагаю, что вы хотите:
found: <the number of occurrences of $1 in /etc/services>
port: <the next free port>
Если эти предположения неверны, дайте мне знать в комментариях, и я отредактирую соответствующим образом.
Сценарий как есть никогда не вернется, если данный порт действительно появляется в /etc/services
, потому что вы никогда не обновляете found
. Таким образом, вы можете подумать, что было бы разумно скопировать строку found=…
в цикл, но это не так! Если вы это сделаете, то скрипт всегда будет возвращать found: 0
. Я думаю, что лучше всего здесь создать функцию:
found () {
grep -co "[[:space:]]$1/" /etc/services
}
, но теперь выходное значение этой функции может напрямую работать как условие. Вы захотите заглушить вывод, чтобы он не отображался вместе с вашим, и тогда вам больше не понадобятся флаги -co
, но вам понадобятся -q
:
while found "$port"; do
Затем, чтобы вернуть количество вхождений порта ввода, это будет
echo "found: $(found "$1")"
или вы можете использовать printf
.
Я думаю, что это сценарий, который вы пытались написать:
#!/bin/sh
found () {
grep -q "[[:space:]]$1/" /etc/services
}
port=$1
while found "$port"; do
port=$(($port+1))
done
printf 'found: %d\nport %d\n' "$(found "$1")" "$port"
Когда я писал это, моей первой реакцией было то, что подсчет строк, соответствующих заданному шаблону, может быть выполнен в awk
, а также grep
.Затем мне пришло в голову, что всю программу можно написать на awk
. В качестве бонуса вот одна из его реализаций (хотя здесь я предполагаю, что /etc/services
сортируется по номеру порта):
#!/usr/bin/awk -f
BEGIN {
FS = "[ \t/][ \t/]*"
ARGC = 2
PORT = ARGV[1]
OPORT = PORT
ARGV[1] = "/etc/services"
}
($2 == OPORT) {
FOUND = FOUND + 1
}
(NEXT == 0 && NR > 1 && $2 > PORT) {
NEXT = ($2 > PORT + 1) ? PORT + 1 : NEXT
PORT = $2
}
END {
printf "found: %d\nport: %d\n", FOUND, (FOUND == 0) ? OPORT : NEXT
}
Я бы попробовал wc -l
и tail
.
Если вы используете bash, это должно работать:
#!/bin/bash
LASTLNFILE=/tmp/lastline # replace with a suitable path
test -f $LASTLNFILE && LASTLN=$(<$LASTLNFILE)
CURLN=$(wc -l $1 | cut -d' ' -f1)
if ((CURLN-LASTLN > 0)); then
tail -n $((CURLN-LASTLN)) $1
fi
echo $CURLN > $LASTLNFILE
П.С. используйте его в качестве фильтра перед вашей программой awk, например. (если вы назвали его 'newlines.sh'):
./newlines.sh <log_file> | awk -f <your_awk_program>`
Я оставляю приведенный выше скрипт в качестве примера того, как этого не делать . Сразу после его написания я понял, что он уязвим для состояния гонки всякий раз, когда файл журнала обновляется во время работы скрипта.
Чистый AWK-подход предпочтительнее:
#!/bin/awk
BEGIN {
lastlinefile = "/tmp/lastlinefile"
getline lastline < lastlinefile
}
NR > lastline && /ERROR FOUND/ {
# do your stuff...
print
}
END { print NR > lastlinefile }
Если вы откроете файл с таким дескриптором, как:
exec 3< /path/to/log/file
Затем вы можете обработать его:
awk '...' <&3
После чего fd 3 укажет на то место, где awk
его оставил.
Через 10 минут из того же вызова оболочки вы можете запустить это
awk '...' <&3
команда еще раз для обработки новых данных.
Если вы хотите сохранить позицию, в которой вы были, чтобы вы могли возобновить чтение из другого вызова оболочки, с ksh93
, вы можете сделать:
#! /usr/bin/env ksh93
file=/path/to/some-file
offset_file=$file.offset
exec 3< "$file"
[ -f "$offset_file" ] && exec 3<#(($(<"$offset_file")))
awk '...' <&3
echo "$(3<#((CUR)))" > "$offset_file"
Или с зш:
#! /usr/bin/env zsh
zmodload zsh/system
file=/path/to/some-file
offset_file=$file.offset
exec 3< $file
[ -f "$offset_file" ] && sysseek -u 3 "$(<$offset_file)"
awk '...' <&3
echo $((systell(3))) > $offset_file
Мне нравится ответ Стефана, потому что он не читает весь файл снова и снова, поэтому я добавляю здесь bash(в Linux )эквивалент его решения (bash не имеет встроенной seek
или tell
способность ). Я бы использовал комментарий, но моя репутация слишком низкая.
LASTPOS=/tmp/saved_pos
exec 3< "$1"
test -f "$LASTPOS" && STARTPOS=$(($(<$LASTPOS)+1))
tail -c "+${STARTPOS:-1}" <&3 | grep "ERROR FOUND"
grep '^pos:' /proc/self/fdinfo/3 | cut -f2 > "$LASTPOS"
Я также заменил команду awk
на grep
, потому что она обычно быстрее. Вы можете направить вывод в команду awk
, если вам нужна дальнейшая обработка.