Как я могу изменить порядок строк в файле?


642

Я хотел бы изменить порядок строк в текстовом файле (или стандартный ввод), сохранив содержимое каждой строки.

Итак, т.е. начиная с:

foo
bar
baz

Я хотел бы закончить с

baz
bar
foo

Для этого есть стандартная утилита командной строки UNIX?


2
Важное примечание об изменении строк: сначала убедитесь, что в вашем файле есть завершающий символ новой строки . В противном случае последние две строки входного файла будут объединены в одну строку в выходном файле (по крайней мере, с использованием, perl -e 'print reverse <>'но, вероятно, это применимо и к другим методам).
— jakub.g


Также довольно почти дубликат (хотя и более старый) unix.stackexchange.com/questions/9356/… . Как и в этом случае, переход на unix.stackexchange.com, вероятно, уместен.
— mc0e

Ответы:


445

Хвост BSD:

tail -r myfile.txt

Ссылка: страницы руководства FreeBSD , NetBSD , OpenBSD и OS X.


120
Просто помните, что опция -r не совместима с POSIX. Приведенные ниже решения sed и awk будут работать даже в самых сложных системах.
— оружие

32
Просто попробовал это на Ubuntu 12.04 и обнаружил, что для моей версии tail (8.13) нет опции -r. Вместо этого используйте «tac» (см. Ответ Михая ниже).
— чудо

12
Галочка должна переместиться ниже к tac. tail -r не работает в Ubuntu 12/13, Fedora 20, Suse 11.
— rickfoosusa

3
tail -r ~ / 1 ~ tail: неверный параметр - r Попробуйте `tail --help 'для получения дополнительной информации. похож на свой новый вариант
— Богдан

6
В ответе, безусловно, должно быть упомянуто, что это только BSD, особенно потому, что OP запросил «стандартную утилиту UNIX». Это не в хвосте GNU, так что это даже не стандарт де-факто.
— DanC

1403

Также стоит упомянуть: tac(Гм, реверс cat). Часть coreutils .

Перевернуть один файл в другой

tac a.txt > b.txt

72
Особенно стоит упомянуть тех, кто использует версию tail без опции -r! (У большинства пользователей Linux есть хвост GNU, у которого нет -r, поэтому у нас есть GNU tac).
— ойленшпигул

11
Просто примечание, потому что люди уже упоминали tac ранее, но tac, похоже, не установлен на OS X. Не то чтобы было сложно написать замену в Perl, но у меня нет реальной.
— Крис Латс

5
Вы можете получить GNU TAC для OS X от Fink. Возможно, вы захотите также получить GNU tail, поскольку он делает некоторые вещи, которых нет у BSD tail.
— ойленшпигул

25
Если вы используете OS X с homebrew, вы можете установить tac с помощью brew install coreutils(устанавливается gtacпо умолчанию).
— Роберт

3
Одна из проблем заключается в том, что если в файле нет завершающей новой строки, первые две строки могут быть объединены в одну строку. echo -n "abc\ndee" > test; tac test,
— CMCDragonkai

161

Есть известные уловки sed :

# reverse order of lines (emulates "tac")
# bug/feature in HHsed v1.5 causes blank lines to be deleted
sed '1!G;h;$!d'               # method 1
sed -n '1!G;h;$p'             # method 2

(Пояснение: добавьте не начальную строку для хранения буфера, измените строку и сохраните буфер, распечатайте строку в конце)

В качестве альтернативы (с более быстрым выполнением) из однострочников awk :

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

Если ты не можешь вспомнить это,

perl -e 'print reverse <>'

В системе с утилитами GNU другие ответы проще, но не во всем мире GNU / Linux ...


4
Из того же источника: awk '{a [i ++] = $ 0} END {for (j = i-1; j> = 0;) напечатать файл [j--]}' * Обе версии sed и awk работают над мой маршрутизатор busybox. 'tac' и 'tail -r' этого не делают.
— оружие

8
Я желаю, чтобы это был принятый ответ. COS SED всегда в наличии, но не так tail -rи TAC.
— ryenus

@ryenus: tacожидается, что он будет обрабатывать произвольные большие файлы, которые не помещаются в памяти (хотя длина строки все еще ограничена). Неясно, sedработает ли решение для таких файлов.
— JFS

Единственная проблема, хотя: будьте готовы ждать :-)
— Антуан Лизе

1
Точнее: код sed находится в O (n ^ 2) и может быть ОЧЕНЬ медленным для больших файлов. Отсюда и мое мнение об альтернативе awk, линейной. Я не пробовал вариант Perl, менее дружественный к трубам.
— Антуан Лизе

71

в конце вашей команды поставьте: | tac

tac делает именно то, что вы просите, он записывает каждый ФАЙЛ в стандартный вывод, в последнюю строку первым.

Так это противоположность кошки :-).


Почему он должен? Пожалуйста, объясните значение tacкоманды, это полезно для новых пользователей, которые могут закончить поиск по той же теме.
— Nic3500

11
Это действительно должен быть принятый ответ. Позор вышеупомянутому имеет так много голосов.
— joelittlejohn

62

Если вы оказались в vimиспользовании

:g/^/m0

5
Связанный: Как изменить порядок строк? на Вим ЮВ
— кенорб

4
Я бы проголосовал, если бы вы кратко объяснили, что он сделал
— mc0e

2
Да, я понял это, но я хотел разбить то, что делают различные части команды vim. Я сейчас посмотрел на ответ @kenorb связанный, который дает объяснение.
— mc0e

5
g означает «сделать это глобально. ^ означает« начало строки ». m означает« переместить строку на новый номер строки ». 0 - на какую строку перейти. 0 означает «начало файла, перед текущей строкой 1». Итак: «Найдите каждую строку, у которой есть начало, и переместите ее в строку № 0.» Вы найдете строку 1 и переместите ее наверх. Ничего не делает. Затем найдите строку 2 и переместите ее выше строки 1 в начало файла. Теперь найдите строку 3 и переместите ее наверх. Повторите это для каждой строки. В конце вы заканчиваете, перемещая последнюю строку наверх. Когда вы закончите, вы перевернули все строки.
— Ронополис

Следует отметить, что глобальная команда: g ведет себя очень специфично по сравнению с простым использованием диапазонов. Например, команда «:% m0» не будет изменять порядок строк, в то время как «:% normal ddggP» (как и «: g / ^ / normal ddggP»). Хороший трюк и объяснение ... О да, забыл жетон "см.
— Натан Чаппелл

51
tac <file_name>

пример:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1

42
$ (tac 2> /dev/null || tail -r)

Попробуйте tac, который работает в Linux, и если это не работает, используйте tail -r, который работает в BSD и OSX.


4
Почему нет tac myfile.txt- что мне не хватает?
— мудрец

8
@sage, к которому можно обратиться tail -rв случае, если tacон недоступен. tacне соответствует POSIX. Ни то, ни другое tail -r. Все еще не надежно, но это повышает шансы на то, что все работает.
— slowpoison

Я вижу - для случаев, когда вы не можете вручную / интерактивно изменить команду, когда она терпит неудачу. Достаточно хорошо для меня.
— мудрец

3
Вам нужен надлежащий тест, чтобы увидеть, если TAC доступен. Что произойдет, если tacдоступно, но не хватает оперативной памяти и поменять местами на полпути, потребляя гигантский поток ввода. Это терпит неудачу, и затем tail -rуспешно обрабатывает остаток потока, давая неправильный результат.
— mc0e

@PetrPeller См. Ответ выше комментария Роберта для OSX использовать homebrew. brew install coreutils и использовать gtacвместо tacи, если вы предпочитаете, добавить tac в качестве псевдонима, gtacесли, например, вам нужен сценарий оболочки, который использовал его кросс-платформенный (Linux, OSX)
— lacostenycoder

24

Попробуйте следующую команду:

grep -n "" myfile.txt | sort -r -n | gawk -F : "{ print $2 }"

вместо выражения gawk я бы сделал что-то вроде этого: sed 's/^[0-9]*://g'
— bng44270

2
почему бы не использовать "nl" вместо grep -n?
— Хороший человек

3
@GoodPerson, nlпо умолчанию не будет нумерация пустых строк. -baОпция доступна на некоторых системах, не не является универсальным (HP / UX приходит на ум, хотя я бы это не так) , тогда grep -nвсегда будет номер каждой строки , которая соответствует (в данном случае пустой) регулярное выражение.
— ghoti

1
Вместо gawk я используюcut -d: -f2-
— Александр Штумпф

17

Просто Баш :) (4.0+)

function print_reversed {
    local lines i
    readarray -t lines

    for (( i = ${#lines[@]}; i--; )); do
        printf '%s\n' "${lines[i]}"
    done
}

print_reversed < file

2
+1 за ответ в bash и за O (n) и за
— неиспользование

2
Попробуйте это с файлом, содержащим строку, -neneneneneneneи посмотрите причину, по которой люди рекомендуют всегда использовать printf '%s\n'вместо echo.
— mtraceur

@mtraceur Я бы согласился с этим на этот раз, так как это общая функция.
— konsolebox

11

Самый простой метод - это использование tac команду. tacявляется catобратным. Пример:

$ cat order.txt
roger shah 
armin van buuren
fpga vhdl arduino c++ java gridgain
$ tac order.txt > inverted_file.txt
$ cat inverted_file.txt
fpga vhdl arduino c++ java gridgain
armin van buuren
roger shah 

1
Я не уверен, почему этот ответ появляется перед тем, как показано ниже, но это всего лишь пачка stackoverflow.com/a/742485/1174784, которая была опубликована много лет назад.
— Анаркат

10

Мне действительно нравится ответ " tail -r ", но мой любимый ответ gawk - ....

gawk '{ L[n++] = $0 } 
  END { while(n--) 
        print L[n] }' file

Протестировано mawkна Ubuntu 14.04 LTS - работает, поэтому оно не является специфичным для GNU awk. +1
— Сергей Колодяжный

n++можно заменить наNR
— karakfa

3

РЕДАКТИРОВАТЬ следующее генерирует случайным образом отсортированный список чисел от 1 до 10:

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') **...**

где точки заменены фактической командой, которая переворачивает список

нолики

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') \
<(tac)

Python: использование [:: - 1] на sys.stdin

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') \
<(python -c "import sys; print(''.join(([line for line in sys.stdin])[::-1]))")

3

Для кросс-ОС (то есть OSX, Linux) решение, которое может использовать tac внутри сценария оболочки, используйте homebrew, как уже упоминалось выше, тогда просто псевдоним tac выглядит так:

Установить lib

Для MacOS

brew install coreutils

Для Linux Debian

sudo apt-get update
sudo apt-get install coreutils 

Затем добавьте псевдоним

echo "alias tac='gtac'" >> ~/.bash_aliases (or wherever you load aliases)
source ~/.bash_aliases
tac myfile.txt

2

Это будет работать как на BSD, так и на GNU.

awk '{arr[i++]=$0} END {while (i>0) print arr[--i] }' filename

1

Если вы хотите изменить файл на месте, вы можете запустить

sed -i '1!G;h;$!d' filename

Это избавляет от необходимости создавать временный файл, а затем удалять или переименовывать оригинал и имеет тот же результат. Например:

$tac file > file2
$sed -i '1!G;h;$!d' file
$diff file file2
$

На основании ответа от ephemient , который сделал почти, но не совсем то, что я хотел.


1

Со мной случается, что я хочу получить последние nстроки очень большого текстового файла эффективно .

Первое, что я попробовал tail -n 10000000 file.txt > ans.txt, но я нашел это очень медленно, дляtail приходится искать местоположение и затем возвращаться, чтобы напечатать результаты.

Когда я понимаю это, я переключаюсь на другое решение: tac file.txt | head -n 10000000 > ans.txt. На этот раз, позиция поиска просто должна переместиться с конца на желаемое место, и это экономит 50% времени !

Взять домой сообщение:

Используйте, tac file.txt | head -n nесли у вас tailнет -rопции.


0

Лучшее решение:

tail -n20 file.txt | tac

Добро пожаловать в переполнение стека! Хотя этот фрагмент кода может решить вопрос, в том числе объяснение действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин, по которым вы предлагаете код. Также постарайтесь не переполнять ваш код пояснительными комментариями, это снижает удобочитаемость кода и пояснений!
— kayess

0

Для пользователей Emacs: C-x h(выберите весь файл), а затем M-x reverse-region. Также работает только для выбора частей или линий и их возврата.


0

Я вижу много интересных идей. Но попробуй мою идею. Направьте ваш текст в это:

рев | tr '\ n' '~' | рев | tr '~' '\ n'

что предполагает, что символ '~' отсутствует в файле. Это должно работать на каждой оболочке UNIX начиная с 1961 года. Или что-то в этом роде.


-1

У меня был тот же вопрос, но я также хотел, чтобы первая строка (заголовок) оставалась сверху. Поэтому мне нужно было использовать силу awk

cat dax-weekly.csv | awk '1 { last = NR; line[last] = $0; } END { print line[1]; for (i = last; i > 1; i--) { print line[i]; } }'

PS тоже работает в cygwin или gitbash


Похоже, что в результате, 1\n20\n19...2\nа не 20\n19...\2\n1\n.
— Марк Бут

-1

Вы можете сделать это с vim stdinи stdout. Вы также можете использовать, exчтобы быть POSIX-совместимым . vimэто просто визуальный режим для ex. На самом деле, вы можете использовать exс vim -eили vim -E(улучшенный exрежим). vimполезен, потому что в отличие от таких инструментов, как sedон, буферизует файл для редактирования, а sedиспользуется для потоков. Вы могли бы использоватьawk , но вам придется вручную буферизовать все в переменной.

Идея состоит в том, чтобы сделать следующее:

  1. Читать со стандартного ввода
  2. Для каждой строки переместите ее в строку 1 (для реверса). Команда есть g/^/m0. Это означает глобально, для каждой строки g; соответствует началу строки, которая соответствует чему-либо ^; переместить его после адреса 0, который является строкой 1m0 .
  3. Распечатай все. Команда есть %p. Это означает для диапазона всех линий %; напечатать строку p.
  4. Принудительно завершить работу без сохранения файла. Команда есть q!. Это значит бросить q; силой !.
# Generate a newline delimited sequence of 1 to 10
$ seq 10
1
2
3
4
5
6
7
8
9
10

# Use - to read from stdin.
# vim has a delay and annoying 'Vim: Reading from stdin...' output
# if you use - to read from stdin. Use --not-a-term to hide output.
# --not-a-term requires vim 8.0.1308 (Nov 2017)
# Use -E for improved ex mode. -e would work here too since I'm not
# using any improved ex mode features.
# each of the commands I explained above are specified with a + sign
# and are run sequentially.
$ seq 10 | vim - --not-a-term -Es +'g/^/m0' +'%p' +'q!'
10
9
8
7
6
5
4
3
2
1
# non improved ex mode works here too, -e.
$ seq 10 | vim - --not-a-term -es +'g/^/m0' +'%p' +'q!'

# If you don't have --not-a-term, use /dev/stdin
seq 10 | vim -E +'g/^/m0' +'%p' +'q!' /dev/stdin

# POSIX compliant (maybe)
# POSIX compliant ex doesn't allow using + sign to specify commands.
# It also might not allow running multiple commands sequentially.
# The docs say "Implementations may support more than a single -c"
# If yours does support multiple -c
$ seq 10 | ex -c "execute -c 'g/^/m0' -c '%p' -c 'q!' /dev/stdin

# If not, you can chain them with the bar, |. This is same as shell
# piping. It's more like shell semi-colon, ;.
# The g command consumes the |, so you can use execute to prevent that.
# Not sure if execute and | is POSIX compliant.
seq 10 | ex -c "execute 'g/^/m0' | %p | q!" /dev/stdin

Как сделать это многоразовым

Я использую скрипт, который я вызываю ved(например, vim editor sed), чтобы использовать vim для редактирования stdin. Добавьте это к файлу, названному vedв вашем пути:

#!/usr/bin/env sh

vim - --not-a-term -Es "$@" +'%p | q!'

Я использую одну +команду вместо +'%p' +'q!', потому что vim ограничивает вас до 10 команд. Таким образом, объединение их позволяет "$@"иметь 9+ команд вместо 8.

Тогда вы можете сделать:

seq 10 | ved +'g/^/m0'

Если у вас нет vim 8, вставьте это vedвместо:

#!/usr/bin/env sh

vim -E "$@" +'%p | q!' /dev/stdin

-3
rev
text here

или

rev <file>

или

rev texthere

Привет, добро пожаловать в Stack Overflow! Когда вы отвечаете на вопрос, вы должны включить какое-то объяснение, например, что автор сделал неправильно и что вы сделали, чтобы это исправить. Я говорю вам об этом, потому что ваш ответ был помечен как некачественный и в настоящее время рассматривается. Вы можете отредактировать свой ответ, нажав кнопку «Редактировать».
— Федерико Гранди

Особенно новые ответы на старые, хорошо отвеченные вопросы нуждаются в достаточном обосновании для добавления еще одного ответа.
— Герт Арнольд

Rev также перевернет текст по горизонтали, что не является желаемым поведением.
— D3l_Gato


-9
sort -r < filename

или

rev < filename

7
sort -rработает, только если вход уже отсортирован, что здесь не так. revпереворачивает символы в строке, но сохраняет порядок строк, что тоже не то, о чем просил Скотти. Так что этот ответ на самом деле не является ответом вообще.
— Александр Штумпф
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.