Як я можу змінити порядок рядків у файлі?


641

Я хотів би змінити порядок рядків у текстовому файлі (або stdin), зберігаючи вміст кожного рядка.

Отже, тобто починаючи з:

foo
bar
baz

Я хотів би закінчити

baz
bar
foo

Чи існує для цього стандартна утиліта командного рядка UNIX?


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


Також майже дублікат (хоча і старший) unix.stackexchange.com/questions/9356/… . Як і в цьому випадку, міграція на unix.stackexchange.com, ймовірно, підходить.
mc0e

Відповіді:


444

Хвіст BSD:

tail -r myfile.txt

Довідка: Сторінки керівництва FreeBSD , NetBSD , OpenBSD та OS X.


120
Пам'ятайте лише, що опція '-r' не відповідає POSIX. Нижчі рішення sed і awk працюватимуть навіть у winkiest системах.
гармати

31
Просто спробував це на Ubuntu 12.04, і виявив, що для моєї версії хвоста (8.13) немає варіанту -r. Використовуйте замість "tac" (див. Відповідь Михайла нижче).
одіозність

12
Галочка повинна рухатися внизу до tac. хвіст -r провалюється на Ubuntu 12/13, Fedora 20, Suse 11.
rickfoosusa

2
tail -r ~ / 1 ~ tail: недійсний варіант - r Спробуйте `tail --help 'для отримання додаткової інформації. виглядають як його новий варіант
Богдан

5
У відповіді, безумовно, слід згадати, що це лише BSD, тим більше, що ОП просила "стандартну утиліту UNIX". Це не в хвості GNU, тому це навіть не є фактичним стандартом.
DanC

1399

Також варто згадати: tac(the, ahem, revers of cat). Частина Coreutils .

Перегортання одного файлу в інший

tac a.txt > b.txt

72
Особливо варто згадати тих, хто використовує версію хвоста без опції -r! (Більшість людей з Linux мають хвіст GNU, у якого немає -r, тому у нас є GNU tac).
oylenshpeegul

11
Лише зауваження, тому що люди вже згадували tac раніше, але tac, здається, не встановлений на OS X. Не те, що було б складно написати замінник в Perl, але у мене немає справжнього.
Кріс Лутц

5
Ви можете отримати GNU tac для OS X від Fink. Можливо, ви також хочете отримати хвіст GNU, оскільки це робить деякі речі, які не має хвоста BSD.
oylenshpeegul

25
Якщо ви використовуєте OS X з домашньою мовою, ви можете встановити tac за допомогою brew install coreutils(встановлюється gtacза замовчуванням).
Роберт

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

161

Є відомі трюки :

# 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 працюють на мій маршрутизатор зайнятих. 'tac' і 'tail -r' не мають.
гармати

8
Я хочу, щоб ця відповідь була прийнятою. coz sed завжди доступний, але не tail -rтаc.
ryenus

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

Тільки проблема: будьте готові дочекатися :-)
Антуан Лізе

1
Точніше: код sed знаходиться в O (n ^ 2), і він може бути ДУЖЕ повільним для великих файлів. Звідси моє твердження за альтернативу awk, лінійну. Я не пробував варіант perl, менш зручний для трубопроводів.
Антуан Лізе

70

наприкінці вашої команди поставте: | tac

tac робить саме те, що ви просите, це "Запишіть кожен ФАЙЛ до стандартного виводу, останній рядок спочатку".

tac - це протилежність коту :-).


Чому він повинен? Поясніть, будь ласка, значення tacкоманди. Це корисно для нових користувачів, які можуть шукати ту саму тему.
Nic3500

11
Це дійсно має бути прийнятою відповіддю. Ганьба вище, так багато голосів.
joelittlejohn

62

Якщо ви випадково vimвикористовуєтесь

:g/^/m0


4
Я би проголосував за це, якщо ви коротко пояснили, що це робило.
mc0e

2
Так, я розумію, але я мав на увазі зруйнувати те, що роблять різні біти команди vim. Зараз я переглянув відповідь @kenorb, пов'язану, яка дає пояснення.
mc0e

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

Слід зазначити, що глобальна команда: g поводиться дуже конкретно проти простого використання діапазонів. Наприклад, команда ":% m0" не змінить порядок рядків, тоді як ":% нормальний ddggP" буде (як буде ": g / ^ / нормальний ddggP"). Приємний трюк та пояснення ... О так, забув жетон "дивіться: допоможіть: g для отримання додаткової інформації" ...
Nathan Chappell


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. Досі не є надійною, але це покращує шанси на роботу.
повільний отрут

Я бачу - для випадків, коли ви не в змозі вручну / інтерактивно змінювати команду, коли вона не працює. Досить добре для мене.
шавлія

3
Вам потрібен належний тест, щоб перевірити, чи доступний tac. Що станеться, якщо tacвона доступна, але вичерпано оперативну пам’ять і поміняється наполовину через споживання гігантського вхідного потоку. Він виходить з ладу, а потім tail -rвдається обробити залишок потоку, даючи неправильний результат.
mc0e

@PetrPeller Дивіться відповідь вище коментар Роберта для OSX використовувати домашню мову. 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
Замість гаука я використовую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 - яка була розміщена роками раніше.
anarcat

10

Мені дуже подобається відповідь " хвіст-р ", але моя улюблена відповідь гоук - це ....

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

Тестований mawkна Ubuntu 14.04 LTS - працює, тому це не є специфічним для GNU awk. +1
Сергій Колодяжний

n++можна замінити наNR
karakfa

3

EDIT, наступне створює випадково відсортований список чисел від 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сценарій оболонки, використовуйте домашню мову, як згадували інші, тоді просто псевдонім так:

Встановіть 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
$

Спираючись на відповідь ефемієнта , яка зробила майже, але не зовсім те, чого я хотів.


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

Я бачу багато цікавих ідей. Але спробуйте мою ідею. Вставте текст у це:

rev | tr '\ n' '~' | rev | 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. Читайте з stdin
  2. Для кожного рядка перемістіть його до рядка 1 (для зворотного зв'язку). Команда є g/^/m0. Це означає глобально, для кожного рядка g; відповідати початку рядка, що відповідає чому-небудь ^; перемістіть його за адресою 0, яка є рядком 1 m0.
  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, як 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! Коли ви відповідаєте на запитання, вам слід включити якесь пояснення, наприклад, що автор зробив неправильно і що ви зробили, щоб виправити це. Я кажу вам про це, оскільки ваша відповідь позначена як неякісна і зараз її переглядають. Ви можете змінити свою відповідь, натиснувши кнопку "Редагувати".
Федеріко Гранді

Esp нові відповіді на старі, добре відповіді на запитання потребують достатнього обґрунтування для додавання ще однієї відповіді.
Герт Арнольд

rev переверне текст також горизонтально, що не є бажаною поведінкою.
D3l_Gato

-4

tail -r працює в більшості систем Linux та MacOS

послідовність 1 20 | хвіст -р


-9
sort -r < filename

або

rev < filename

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