знайдіть лінії, починаючи з рядка


10

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

Наприклад для наступного файлу:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Cyyyyyyyyy
Czzzzzzzzz
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd
Ceeeeee

Існує більше одного рядка, що починається з "C", тому я хочу, щоб цей файл був знайдений командою.
Наприклад для наступного файлу:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd

Завжди є один рядок, що починається з "C", я не хочу цього файлу. Я думав скористатись a grepчи a, sedале не знаю точно, як це зробити. Можливо, використовуючи rgexp ^C.*$^Cабо щось подібне. Будь-яка ідея?


У Cдругому прикладі починаються два рядки .
cuonglm

5
Це питання незрозуміле. Ви шукаєте файли, які мають більше одного послідовного рядка C?
Graeme

Так, це те, що я хочу. Вибачте за непорозуміння.
Jérémie

2
@terdon, це виглядає як багаторядковий пошук з -P працював до 2.5.4 і більше не після цього, хоча я не можу знайти нічого в журналі змін, який би пояснив, чому.
Стефан Шазелас

1
@Graeme, можливо, ви захочете відновити свою відповідь, дивіться коментар Стефана, мабуть, це працює і для деяких старих grepверсій.
terdon

Відповіді:


5

З pcregrep:

pcregrep -rMl '^C.*\nC' .

POSIXly:

find . -type f -exec awk '
  FNR==1 {last=0; printed=0; next}
  printed {next}
  /^C/ {if (last) {print FILENAME; printed=1; nextfile} else last=1; next}
  {last=0}' {} +

(хоча це означає повністю читати всі файли з тими awkреалізаціями, які не підтримують nextfile).


З версіями GNU grepдо 2.5.4:

grep -rlP '^C.*\nC' .

Схоже, працює, але це випадково, і це не гарантовано.

Перш ніж це було визначено в 2.6 ( цим зобов'язанням ), GNU grepне помітив, що функція пошуку pcre, яку він використовує, буде відповідати всьому буфері, який зараз обробляється grep, викликаючи всілякі дивовижні поведінки. Наприклад:

grep -P 'a\s*b'

відповідатиме файлу, що містить:

bla
bla

Це відповідатиме:

printf '1\n2\n' | grep -P '1\n2'

Але це:

(printf '1\n'; sleep 1; printf '2\n') | grep -P '1\n2'

Або:

(yes | head -c 32766; printf '1\n2\n') > file; grep -P '1\n2' file

не буде (як 1\n2\nце через два буфери, оброблені grep).

Така поведінка в кінцевому підсумку була задокументована:

15- Як я можу відповідати між лініями?

Стандартний греп не може цього зробити, оскільки це принципово лінійно. Тому просто використання класу символів '[: space:]' не відповідає новим рядкам так, як ви могли очікувати. Однак, якщо ваш греп компілюється з увімкненими шаблонами Perl, модифікатор Perl '(який робить'. 'Відповідає новому рядку):

     printf 'foo\nbar\n' | grep -P '(?s)foo.*?bar'

Після того, як це було зафіксовано в 2.6, документація не була змінена (я колись там повідомляла ).


Чи є якась причина не використовувати, exitа -exec \;не nextfile?
terdon

@terdon, це означатиме запуск одного awkфайлу. Ви хочете зробити це лише в тому випадку, якщо ваша awkпідтримка не підтримується, nextfileі у вас є велика частка файлів, які є великими і мають відповідні рядки на початку файлу.
Стефан Шазелас

Як щодо цієї методики grep (я думаю, що з більш новими версіями grep GNU), яка полегшує багаторядкові збіги, зробивши весь файл схожим на одну рядок, встановивши термінатор рядка на NUL - ви б знали, чи є в ньому якісь обмеження?
iruvar

1
@ 1_CR, Це би завантажило весь файл у пам'ять, якщо в ньому немає символу NUL і, якщо передбачається, що рядки не містять символів NUL. Також зверніть увагу , що старі версії GNU Grep (яких OP має) не можуть використовувати -zз -P. Там немає \Nбез -P, ви повинні були б написати її , $'[\01-\011\013-\0377]'яка буде працювати тільки в локалей C (див thread.gmane.org/gmane.comp.gnu.grep.bugs/5187 )
Stéphane Chazelas

@StephaneChazelas, дуже корисна деталь, дякую
iruvar

2

З awk:

awk '{if (p ~ /^C/ && $1 ~ /^C/) print; p=$1}' afile.txt

Це надрукує вміст файлу, якщо є послідовні рядки, що починаються з a C. Вираз (p ~ /^C/ && $1 ~ /^C/)буде розглядатись у послідовних рядках у файлі та оцінить як істинне, якщо перший символ в обох збігається C. Якщо це так, рядок буде надруковано.

Для того щоб знайти всі файли, що мають такий зразок, ви можете запустити вищезгаданий awk за допомогою findкоманди:

find /your/path -type f -exec awk '{if (p ~ /^C/ && $1 ~ /^C/) {print FILENAME; exit;} p=$1}' {} \;

У цій команді find+ execпройде кожен з файлів і виконає подібну awkфільтрацію на кожному файлі та надрукує його ім'я за допомогою, FILENAMEякщо вираз awk оцінено як істинне. Щоб уникнути друку FILENAMEдекількох разів для одного файлу з декількома збігами, використовується exitоператор (спасибі @terdon).


Моє запитання було недостатньо чітким, я хочу знати назву файлів з більш ніж однією послідовною лінією, починаючи зC
Jérémie

@ Jérémie Я оновив свою відповідь.
mkc

Не могли б ви додати пояснення, як це працює? Крім того, немає необхідності flag, просто exitзамість цього. Таким чином, вам не потрібно тримати обробку файлів після того, як буде знайдено збіг.
тердон

2

Ще один варіант з GNU sed:

Для одного файлу:

sed -n -- '/^C/{n;/^C/q 1}' "$file" || printf '%s\n' "$file"

(хоча він також повідомить про файли, які він не може прочитати).

Для find:

find . -type f ! -exec sed -n '/^C/{n;/^C/q 1}' {} \; -print

Проблему з друкованими нечитабельними файлами можна уникнути, записавши їх:

find . -type f -size +2c -exec sed -n '$q1;/^C/{n;/^C/q}' {} \; -print

Чи можете ви детально розказати sed -n '$q1;/^C/{n;/^C/q}'?
Jérémie

Хтось мені пояснить?
Jérémie

@ Jérémie $q1- змушує sed вийти з помилкою, якщо шаблон не знайдено. Він також закінчиться помилкою, якщо з файлом щось не так (він нечитабельний або зламаний). Таким чином, він вийде з 0 статусом виходу лише у випадку, якщо знайдеться шаблон і він буде переданий для друку. Розлучитися /^C/{n;/^C/qдосить просто. Якщо він знайде рядок, який починається з C, він прочитає наступний рядок, а якщо він також починається з C, він вийде з нульовим статусом виходу.
пік

1

Якщо ваші файли досить малі, щоб їх можна було прочитати в пам'яті:

perl -000ne 'print "$ARGV\n" if /^C[^\n]*\nC/sm' *

Пояснення:

  • - 000: встановлено \n\nяк роздільник записів, увімкнено режим абзацу, який буде розглядати абзаци (розділені послідовними новими рядками) як окремі рядки.
  • -ne: застосувати заданий сценарій як аргумент -eдо кожного рядка вхідних файлів.
  • $ARGV : це файл, який зараз обробляється
  • /^C[^\n]*\nC/: збіг Cна початку рядка (див. опис smмодифікаторів нижче, чому це працює тут) з наступними 0 або більше символами, які не є новим рядком, новим рядком та іншим C. Іншими словами, знайдіть послідовні рядки, починаючи з C. * //sm: ці модифікатори відповідності (як задокументовано [тут]):

    • m : обробляти рядок як кілька рядків. Тобто, змініть "^" і "$", щоб відповідати початковому або кінцевому рядку лише в лівому і правому кінцях рядка, щоб їх відповідати будь-де в межах рядка.

    • s : обробляти рядок як один рядок. Тобто, зміни "." щоб відповідати будь-якому символу, навіть новому рядку, який зазвичай не відповідає.

Ви також можете зробити щось потворне, як-от:

for f in *; do perl -pe 's/\n/%%/' "$f" | grep -q 'C[^%]*%%C' && echo "$f"; done

Тут perlкод замінює нові рядки %%таким чином, припускаючи, що %%у вхідному файлі у вас немає (великий, якщо звичайно), grepвідповідні рядки будуть відповідати послідовним рядкам C.


1

РІШЕННЯ:

( set -- *files ; for f ; do (
set -- $(printf %c\  `cat <$f`)
while [ $# -ge 1 ] ;do [ -z "${1#"$2"}" ] && {
    echo "$f"; break ; } || shift
done ) ; done )

DEMO:

Спочатку ми створимо тестову базу:

abc="a b c d e f g h i j k l m n o p q r s t u v w x y z" 
for l in $abc ; do { i=$((i+1)) h= c= ;
    [ $((i%3)) -eq 0 ] && c="$l" h="${abc%"$l"*}"
    line="$(printf '%s ' $h $c ${abc#"$h"})"
    printf "%s$(printf %s $line)\n" $line >|/tmp/file${i}
} ; done

Створене вище створює 26 файлів з /tmpназвами file1-26. У кожному файлі є 27 або 28 рядків, що починаються з літер, a-zа далі - решта алфавіту. Кожен 3-й файл містить два послідовних рядки, у яких дублюється перший символ.

Зразок:

cat /tmp/file12
...
aabcdefghijkllmnopqrstuvwxyz
babcdefghijkllmnopqrstuvwxyz
cabcdefghijkllmnopqrstuvwxyz
...
kabcdefghijkllmnopqrstuvwxyz
labcdefghijkllmnopqrstuvwxyz
labcdefghijkllmnopqrstuvwxyz
mabcdefghijkllmnopqrstuvwxyz
...

І коли я змінююсь:

set -- *files

до:

set -- /tmp/file[0-9]*

Я отримав...

ВИХІД:

/tmp/file12
/tmp/file15
/tmp/file18
/tmp/file21
/tmp/file24
/tmp/file3
/tmp/file6
/tmp/file9

Отже, коротко, рішення працює так:

sets позиції додаткової оболонки для всіх ваших файлів і для кожного

sets вкладені позиції додаткової оболонки до першої літери кожного рядка у кожному файлі під час циклу.

[ tests ]якщо $1заперечення $2вказує на відповідність, і якщо так

echoesім'я файлу, потім breaks поточна ітерація циклу

else shifts до наступного окремого символу, щоб спробувати ще раз


0

Цей скрипт використовує grepта cutдля отримання номерів рядків відповідних рядків, а також перевіряє наявність будь-яких двох послідовних номерів. Файл передбачається, що дійсне ім'я файлу передано в якості першого аргументу до сценарію:

#!/bin/bash

checkfile () {
 echo checking $1
 grep -n -E "^C.*$" $1 | cut -d: -f1 | while read linenum
     do
        : $[ ++PRV ] 
        if [ $linenum == $PRV ]; then return 1; fi
        PRV=$linenum
     done
     return 0
}

PRV="-1"
checkfile $1
if [ $? == 0 ]; then
   echo Consecutive matching lines found in file $1
fi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.