Как я могу напечатать строки из файла в обратном направлении (без использования «TAC»)?


26

Я хочу напечатать строки из файла в обратном направлении без использования tacкоманды. Есть ли другое решение, чтобы сделать такую ​​вещь с Bash?


Ответы:


33

Использование sedдля эмуляции tac:

sed '1!G;h;$!d' ${inputfile}

3
Это хорошее решение, но некоторые объяснения того, как это работает, будут еще лучше.
Чен Леви

10
Это знаменитый sedлайнер. Смотрите "36. Обратный порядок строк (эмулируйте" tac "Unix command)." в известном Sed One-Liners объяснил для полного объяснения того, как это работает.
Johnsyweb

6
Возможно, стоит отметить: «Эти два однострочника на самом деле используют много памяти, потому что перед печатью они хранят весь файл в буфере хранения в обратном порядке. Избегайте этих однострочников для больших файлов».
Мистер Шикаданс

Так что сделайте все остальные ответы (кроме, может быть, одного, который использует sort- есть вероятность, что он будет использовать временный файл).
Random832

1
Обратите внимание, что tac быстрее для обычных файлов, потому что он читает файл в обратном направлении. Для каналов он должен делать то же самое, что и другие решения (хранить в памяти или во временных файлах), поэтому он не значительно быстрее.
Стефан Шазелас


6

awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }' file.txt

через awk один лайнер


4
Короче:awk 'a=$0RS a{}END{printf a}'
ниндзя

@ninjalj: это может быть короче, но это становится чрезвычайно медленным, поскольку размер файла увеличивается. Я сдался после ожидания в течение 2 минут 30 секунд. but your first perl reverse <> `это лучший / самый быстрый ответ на странице (для меня), в 10 раз быстрее, чем этот awkответ (все awk anseres примерно одинаковы по времени)
Peter.O

1
Илиawk '{a[NR]=$0} END {while (NR) print a[NR--]}'
Стефан Шазелас

5

Как вы просили сделать это в bash, вот решение, которое не использует awk, sed или perl, а только функцию bash:

reverse ()
{
    local line
    if IFS= read -r line
    then
        reverse
        printf '%s\n' "$line"
    fi
}

Выход из

echo 'a
b
c
d
' | reverse

является

d
c
b
a

Как и ожидалось.

Но имейте в виду, что строки хранятся в памяти, по одной строке в каждом рекурсивно вызванном экземпляре функции. Так осторожно с большими файлами.


1
Он быстро становится практически неосуществимо медленным, когда размер файла увеличивается, по сравнению с даже самым медленным из других ответов, и, как вы предположили, он довольно легко уносит память: * bash рекурсивная функция ... но это интересная идея. Ошибка сегментации *
Peter.O

4

Вы можете передать это через:

awk '{print NR" "$0}' | sort -k1 -n -r | sed 's/^[^ ]* //g'

Перед awkкаждой строкой ставится префикс номера строки, за которым следует пробел. sortИзменяет порядок строк при сортировке по первому полю (номер строки) в обратном порядке, числовой стиль. И sedполоски с номера строк.

Следующий пример показывает это в действии:

pax$ echo 'a
b
c
d
e
f
g
h
i
j
k
l' | awk '{print NR" "$0}' | sort -k1 -n -r | sed 's/^[^ ]* //g'

Это выводит:

l
k
j
i
h
g
f
e
d
c
b
a

Хорошо. Благодарность! Я попробую это. И спасибо за комментарии!

5
cat -nдействует так же, какawk '{print NR" "$0}'
Чен Леви

2
Я думаю, что это называется преобразованием Шварца , или украшением-сортировкой-
украшением

Этот общий подход хорош в том смысле, что он обрабатывает файлы на диске, если задача слишком велика для разумного выполнения в памяти. Это может быть мягче, если вы используете временные файлы между шагами, а не трубы.
mc0e

1

В perl:

cat <somefile> | perl -e 'while(<>) { push @a, $_; } foreach (reverse(@a)) { print; }'

2
или корочеperl -e 'print reverse<>'
ниндзяль

2
( На самом деле, есть более короткий, но это действительно некрасиво, свидетель его ужас: perl -pe '$\=$_.$\}{')
ninjalj

@Frederik Deweerdt: Быстро, но теряет первую строчку ... @ ninjalj: reverse<)быстро: хорошо! но «действительно уродливый» чрезвычайно медленный, так как количество линий увеличивается. !! ....
Peter.O

@ Peter.O -nспасибо, спасибо.
Фредерик Дьюердт

Хорошая вещь об этом состоит в том, что содержимое файла не обязательно читается в память (кроме, возможно, кусками sort).
Кусалананда

0

BASH-только решение

прочитать файл в массив bash (одна строка = один элемент массива) и распечатать массив в обратном порядке:

i=0 

while read line[$i] ; do
    i=$(($i+1))
done < FILE


for (( i=${#line[@]}-1 ; i>=0 ; i-- )) ; do
    echo ${line[$i]}
done

Попробуйте сделать это с помощью отступов ... Версия от Philfr'а немного лучше, но все еще очень крутая, так что, когда дело доходит до обработки текста, никогда не используйте while..read.
don_crissti

Используйте IFS=''и read -rдля предотвращения всех видов побегов и конечного удаления IFS от обворовывания. Я думаю, что mapfile ARRAY_NAMEвстроенная функция bash - лучшее решение для чтения массивов.
Arthur2e5

0

Bash, mapfileупомянутое в комментариях к fiximan, и, возможно, лучшая версия

# last [LINES=50]
_last_flush(){ BUF=("${BUF[@]:$(($1-LINES)):$1}"); } # flush the lines, can be slow.
last(){
  local LINES="${1:-10}" BUF
  ((LINES)) || return 2
  mapfile -C _last_flush -c $(( (LINES<5000) ? 5000 : LINES+5 )) BUF
  BUF=("${BUF[@]}") # Make sure the array subscripts make sence, can be slow.
  ((LINES="${#BUF[@]}" > LINES ? LINES : "${#BUF[@]}"))
  for ((i="${#BUF[@]}"; i>"${#BUF[@]}"-LINES; i--)); do echo -n "${BUF[i]}"; done
}

Его производительность в основном сопоставима с sedрешением и становится быстрее по мере уменьшения количества запрашиваемых строк.



0
  • Пронумеруйте строки с помощью nl
  • сортировать в обратном порядке по номеру
  • убрать числа с помощью sed

как показано здесь:

echo 'e
> f
> a
> c
> ' | nl | sort -nr | sed -r 's/^ *[0-9]+\t//'

Результат:

c
a
f
e

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.