сортировать, но держать строку заголовка вверху

56

Я получаю вывод из программы, которая сначала создает одну строку, которая представляет собой группу заголовков столбцов, а затем группу строк данных. Я хочу вырезать различные столбцы этого вывода и просматривать их отсортированные по различным столбцам. Без заголовков, режущие и сортировка легко осуществляются с помощью -kопции , чтобы sortвместе с cutили awkпросмотреть подмножество столбцов. Однако этот метод сортировки смешивает заголовки столбцов с остальными строками вывода. Есть ли простой способ держать заголовки наверху?

jonderry
источник
1
Я наткнулся на следующую ссылку . Однако я не могу заставить эту технику { head -1; sort; }работать. Он всегда удаляет кучу текста после первой строки. кто-нибудь знает, почему это произошло?
Jonderry
1
Я подозреваю, что это потому, что headчитает более одной строки в буфер и выбрасывает большую часть ее. У моей sedидеи была та же проблема.
Энди
@jonderry - этот метод работает только с lseekвозможностью ввода, поэтому он не будет работать при чтении из канала. Это будет работать, если вы перенаправите файл, >outfileа затем запустите{ head -n 1; sort; } <outfile
don_crissti

Ответы:

58

Похищение идеи Энди и превращение ее в функцию, облегчающую ее использование:

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}

Теперь я могу сделать:

$ ps -o pid,comm | body sort -k2
  PID COMMAND
24759 bash
31276 bash
31032 less
31177 less
31020 man
31167 man
...

$ ps -o pid,comm | body grep less
  PID COMMAND
31032 less
31177 less
Mikel
источник
ps -C COMMANDможет быть более уместным, чем grep COMMAND, но это всего лишь пример. Кроме того, вы не можете использовать, -Cесли вы также использовали другой вариант выбора, например -U.
Микель
Или, может быть, это следует назвать body? Как в body sortили body grep. Мысли?
Микель
3
Переименован из headerв body, потому что вы делаете действие на теле. Надеюсь, это имеет больше смысла.
Микель
2
Не забудьте призвать bodyвсех последующих участников трубопровода:ps -o pid,comm | body grep less | body sort -k1nr
епископ
1
@ Тим Вы можете просто написать <foo body sort -k2или body sort -k2 <foo. Всего один лишний персонаж из того, что вы хотели.
Микель,
37

Вы можете держать заголовок вверху вот так с помощью bash:

command | (read -r; printf "%s\n" "$REPLY"; sort)

Или сделайте это с Perl:

command | perl -e 'print scalar (<>); print sort { ... } <>'
Энди
источник
2
+1 потрясающе. Думаю, стоит связать в качестве функции оболочки.
Микель
1
+1, есть какая-то причина, почему подоболочка предпочтительнее, или в {}порядке вместо ()?
Jonderry
2
IFS=отключает разделение слов при чтении ввода. Я не думаю, что это необходимо при чтении $REPLY. echoрасширит экранирование обратной косой черты, если xpg_echoустановлено (не по умолчанию); printfв этом случае безопаснее. echo $REPLYбез кавычек будет сгущать пробелы; Я думаю echo "$REPLY"должно быть хорошо. read -rнеобходим, если входные данные могут содержать экранирование обратной косой черты. Отчасти это может зависеть от версии bash.
Энди
1
@Анди: Ух ты, ты прав, разные правила для read REPLY; echo $REPLY(лишних пробелов) и read; echo $REPLY(не).
Микель,
1
@Andy: IIRC, значение по умолчанию xpg_echoзависит от вашей системы, например, от Solaris, я думаю, что по умолчанию установлено значение true. Вот почему Жиль printfтак любит : это единственное, что предсказуемо.
Микель,
23

Я нашел хорошую версию awk, которая прекрасно работает в скриптах:

awk 'NR == 1; NR > 1 {print $0 | "sort -n"}'
Майкл Кун
источник
1
Мне это нравится, но это требует небольшого объяснения - канал находится внутри скрипта awk. Как это работает? Это вызывает sortкоманду извне? Кто-нибудь знает хотя бы ссылку на страницу, объясняющую использование канала в awk?
Wildcard
@Wildcard вы можете проверить официальную страницу руководства или этот учебник .
Lapo
4

Резко, но эффективно: перед сортировкой добавьте 0все строки заголовка и 1все остальные строки. Уберите первый символ после сортировки.

… |
awk '{print (NR <= 2 ? "0 " : "1 ") $0}' |
sort -k 1 -k… |
cut -b 3-
Жиль "ТАК - перестань быть злым"
источник
3

Вот некоторый волшебный шум Perl-строк, через который вы можете передать свой вывод, чтобы отсортировать все, но оставить первую строку наверху: perl -e 'print scalar <>, sort <>;'

Райан Томпсон
источник
2

Я попробовал command | {head -1; sort; }решение и могу подтвердить, что оно действительно все испортило - headчитает в несколько строк из канала, а затем выводит только первое. Таким образом, остальная часть вывода, который head не был прочитан, передается в sort-NOT остальная часть вывода, начиная со строки 2!

В результате вы пропускаете строки (и одну частичную строку!), Которые были в начале вывода вашей команды (за исключением того, что у вас все еще есть первая строка) - факт, который легко подтвердить, добавив канал wcв конце вышеупомянутый конвейер - но это чрезвычайно трудно отследить, если вы этого не знаете! Я потратил не менее 20 минут, пытаясь понять, почему у меня была неполная строка (первые 100 байт или около того, обрезанная) в моем выводе, прежде чем ее решить.

То, что я в итоге сделал, что работало прекрасно и не требовало запуска команды дважды, было:

myfile=$(mktemp)
whatever command you want to run > $myfile

head -1 $myfile
sed 1d $myfile | sort

rm $myfile

Если вам нужно поместить вывод в файл, вы можете изменить это следующим образом:

myfile=$(mktemp)
whatever command you want to run > $myfile

head -1 $myfile > outputfile
sed 1d $myfile | sort >> outputfile

rm $myfile
Wildcard
источник
Вы можете использовать headвстроенную в ksh93 lineутилиту или утилиту (в системах, где она еще есть) или gnu-sed -u qили IFS=read -r line; printf '%s\n' "$line", которая читает входные данные по одному байту за раз, чтобы избежать этого.
Стефан Шазелас
1

Я думаю, что это проще всего.

ps -ef | ( head -n 1 ; sort )

или это, возможно, быстрее, так как не создает субоболочку

ps -ef | { head -n 1 ; sort ; }

Другое крутое использование

перетасовать строки после строки заголовка

cat file.txt |  ( head -n 1 ; shuf )

обратные строки после строки заголовка

cat file.txt |  ( head -n 1 ; tac )
user2449151
источник
2
См. Unix.stackexchange.com/questions/11856/… . Это не очень хорошее решение.
Wildcard
1
Не работает, cat file | { head -n 1 ; sort ; } > file2только покажи голову
Питер Краусс
0
command | head -1; command | tail -n +2 | sort
Sarva
источник
4
Это начинается commandдва раза. Поэтому он ограничен некоторыми конкретными командами. Однако для запрошенной psкоманды в примере это будет работать.
Джофель
0

Просто и понятно!

<command> | head -n 1; <command> | sed 1d | sort <....>
  • sed nd ---> 'n' указывает номер строки, а 'd' обозначает удаление.
Jatsui
источник
1
Так же, как Джофель прокомментировал полтора года назад ответ Сарвы, это начинается commandдважды. Так что не очень подходит для использования в трубопроводе.
Подстановочный
0

Я пришел сюда в поисках решения для команды w. Эта команда показывает детали того, кто вошел в систему и что они делают.

Чтобы показать отсортированные результаты, но с верхними заголовками (есть две строки заголовков), я остановился на:

w | head -n 2; w | tail -n +3 | sort

Очевидно, что команда запускается wдважды и поэтому может не подходить для всех ситуаций. Тем не менее, к его преимуществам это значительно легче запомнить.

Обратите внимание, что tail -n +3означает «показать все строки, начиная с 3-го и далее» (подробнее см. man tail).

Роберт
источник
-2

Попробуйте сделать:

wc -l file_name | tail -n $(awk '{print $1-1}') file_name | sort
Барри
источник
3
я не понимаю
Pierre.Vriens