Как использовать sed для извлечения строк в указанном порядке?
У меня есть файл длиной ~ 50 000 строк, и мне нужно получить определенные строки. Я пробовал следующую команду:
sed -n 'Np;Np;Np' inputFile.txt > outputFile.txt
('N' - это конкретные строки, которые я хочу извлечь)
Это отлично работает, но команда извлекает строки в ORDER (т.е. RE-ORDERS мой вход) ex. если я попробую:
sed -n '200p;33p;40,000p' inputFile.txt > outputFile.txt
Я получаю текстовый файл с строками, заказанными как: 33, 200, 40 000 (что не работает для моей цели). Есть ли способ поддерживать порядок, в котором строки появляются в команде?
Ответы
Ответ 1
Вы должны удерживать строку 33 до тех пор, пока не увидите строку 200:
sed -n '33h; 200{p; g; p}; 40000p' file
Дополнительную информацию см. в руководстве: https://www.gnu.org/software/sed/manual/html_node/Other-Commands.html
awk
может быть более читаемым:
awk '
NR == 33 {line33 = $0}
NR == 200 {print; print line33}
NR == 40000 {print}
' file
Если у вас есть произвольное количество строк для печати в определенном порядке, вы можете обобщить это:
awk -v line_order="11 3 5 1" '
BEGIN {
n = split(line_order, inorder)
for (i=1; i<=n; i++) linenums[inorder[i]]
}
NR in linenums {cache[NR]=$0}
END {for (i=1; i<=n; i++) print cache[inorder[i]]}
' file
Ответ 2
с perl
, сохраняет строки ввода в хэш-переменной с номером строки как ключ
$ seq 12 20 | perl -nle '
@l = (5,2,3,1);
$a{$.} = $_ if( grep { $_ == $. } @l );
END { print $a{$_} foreach @l } '
16
13
14
12
-
$.
- номер строки, а grep { $_ == $. } @l
проверяет, присутствует ли этот номер строки в массиве @l
, который содержит нужные строки в требуемом порядке
как однострочное объявление @l
внутри BEGIN
, чтобы избежать инициализации каждой итерации, а также без пустых строк, если номер строки выходит за пределы диапазона:
$ seq 50000 > inputFile.txt
$ perl -nle 'BEGIN{@l=(200,33,40000)} $a{$.}=$_ if(grep {$_ == $.} @l); END { $a{$_} and print $a{$_} foreach (@l) }' inputFile.txt > outputFile.txt
$ cat outputFile.txt
200
33
40000
Для достаточно малого ввода можно сохранить строки в массиве и потребовать индексы печати. Обратите внимание на то, что настройка начинается с начала индекса с помощью 0
$ seq 50000 | perl -e '$l[0]=0; push @l,<>; print @l[200,33,40000]'
200
33
40000
Решение с head
и tail
комбо:
$ for i in 200 33 40000; do head -"${i}" inputFile.txt | tail -1 ; done
200
33
40000
Сравнение производительности для входного файла seq 50000 > inputFile.txt
$ time perl -nle 'BEGIN{@l=(200,33,40000)} $a{$.}=$_ if(grep {$_ == $.} @l); END { $a{$_} and print $a{$_} foreach (@l) }' inputFile.txt > outputFile.txt
real 0m0.044s
user 0m0.036s
sys 0m0.000s
$ time awk -v line_order="200 33 40000" '
BEGIN {
n = split(line_order, inorder)
for (i=1; i<=n; i++) linenums[inorder[i]]
}
NR in linenums {cache[NR]=$0}
END {for (i=1; i<=n; i++) print cache[inorder[i]]}
' inputFile.txt > outputFile.txt
real 0m0.019s
user 0m0.016s
sys 0m0.000s
$ time for i in 200 33 40000; do sed -n "${i}{p;q}" inputFile.txt ; done > outputFile.txt
real 0m0.011s
user 0m0.004s
sys 0m0.000s
$ time sed -n '33h; 200{p; g; p}; 40000p' inputFile.txt > outputFile.txt
real 0m0.009s
user 0m0.008s
sys 0m0.000s
$ time for i in 200 33 40000; do head -"${i}" inputFile.txt | tail -1 ; done > outputFile.txt
real 0m0.007s
user 0m0.000s
sys 0m0.000s
Ответ 3
Можете ли вы использовать и другие команды bash? В этом случае это работает:
for i in 200 33 40000; do
sed -n "${i}p" inputFile.txt
done > outputFile.txt
Вероятно, это медленнее, чем использование массива внутри sed, но это более практично.