как использовать sed для извлечения строк в указанном порядке?

#linux #bash #unix #sed

#linux #bash #unix #sed

Вопрос:

У меня есть файл длиной ~ 50 000 строк, и мне нужно получить определенные строки. Я попробовал следующую команду :

 sed -n 'Np;Np;Np' inputFile.txt > outputFile.txt
  

(‘N’ — это конкретные строки, которые я хочу извлечь)

Это работает нормально, но команда извлекает строки по ПОРЯДКУ (т.Е. ПЕРЕУПОРЯДОЧИВАЕТ мой ввод), например. если я попытаюсь:

 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
  

Комментарии:

1. Я тестировал sed и первые решения awk, но строка 33 выводит пустую строку. последнее решение awk выполняется правильно.

Ответ №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, но это более практично.

Комментарии:

1. Если вы собираетесь анализировать файл несколько раз, то, по крайней мере, завершите работу после печати нужной строки: sed -n "${i} {p;q}"