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

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 ...

ephemient
источник
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
DerMike
источник
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
Jins
источник
42
$ (tac 2> /dev/null || tail -r)

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

DigitalRoss
источник
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 }"
kenorb
источник
вместо выражения 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
konsolebox
источник
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 
Yekatandilburg
источник
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
lacostenycoder
источник
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опции.

youkaichao
источник
0

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

tail -n20 file.txt | tac
ITNM
источник
Добро пожаловать в переполнение стека! Хотя этот фрагмент кода может решить вопрос, в том числе объяснение действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин, по которым вы предлагаете код. Также постарайтесь не переполнять ваш код пояснительными комментариями, это снижает удобочитаемость кода и пояснений!
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

KIC
источник
Похоже, что в результате, 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
dosentmatter
источник
-3
rev
text here

или

rev <file>

или

rev texthere
user13575069
источник
Привет, добро пожаловать в Stack Overflow! Когда вы отвечаете на вопрос, вы должны включить какое-то объяснение, например, что автор сделал неправильно и что вы сделали, чтобы это исправить. Я говорю вам об этом, потому что ваш ответ был помечен как некачественный и в настоящее время рассматривается. Вы можете отредактировать свой ответ, нажав кнопку «Редактировать».
Федерико Гранди
Особенно новые ответы на старые, хорошо отвеченные вопросы нуждаются в достаточном обосновании для добавления еще одного ответа.
Герт Арнольд
Rev также перевернет текст по горизонтали, что не является желаемым поведением.
D3l_Gato
-4

tail -r работает в большинстве систем Linux и MacOS

сек 1 20 | хвост -r

Богдана
источник
-9
sort -r < filename

или

rev < filename
Йокер Рекой
источник
7
sort -rработает, только если вход уже отсортирован, что здесь не так. revпереворачивает символы в строке, но сохраняет порядок строк, что тоже не то, о чем просил Скотти. Так что этот ответ на самом деле не является ответом вообще.
Александр Штумпф