Використовуйте команду find, але виключайте файли з двох каталогів


86

Я хочу знайти файли, які закінчуються на _peaks.bed, але виключити файли в папках tmpі scripts.

Моя команда така:

 find . -type f \( -name "*_peaks.bed" ! -name "*tmp*" ! -name "*scripts*" \)

Але це не спрацювало. Файли в папці tmpта scriptпапці все одно відображатимуться.

Хтось має ідеї з цього приводу?

Відповіді:


190

Ось як ви можете вказати це за допомогою find:

find . -type f -name "*_peaks.bed" ! -path "./tmp/*" ! -path "./scripts/*"

Пояснення:

  • find . - Почати пошук з поточного робочого каталогу (рекурсивно за замовчуванням)
  • -type f- Вкажіть, findщо вам потрібні лише файли в результатах
  • -name "*_peaks.bed" - Шукайте файли з іменем, що закінчується на _peaks.bed
  • ! -path "./tmp/*" - Виключіть усі результати, шлях яких починається з ./tmp/
  • ! -path "./scripts/*" - Також виключіть усі результати, шлях яких починається з ./scripts/

Тестування рішення:

$ mkdir a b c d e
$ touch a/1 b/2 c/3 d/4 e/5 e/a e/b
$ find . -type f ! -path "./a/*" ! -path "./b/*"

./d/4
./c/3
./e/a
./e/b
./e/5

Ви були досить близько, -nameваріант враховує лише базове ім'я, де як -pathвраховує весь шлях =)


Хороша робота. Тим не менш, ви забули одну з речей, яку хотів OP, щоб знайти файли, що закінчуються на _peaks.bed.
alex

2
Тут використовується ряд розширень у GNU find, але оскільки питання поставлене з позначкою Linux, це не проблема. Гарна відповідь.
Джонатан Леффлер

1
Коротке зауваження: якщо ви використовуєте .на початковому запиті пошуку, ви повинні використовувати його в кожному виключеному шляху. Відповідність шляху досить сувора, вона не робить нечіткого пошуку. Отже, якщо ви використовуєте, find / -type f -name *.bed" ! -path "./tmp/"це не спрацює. вам потрібно ! -path "/tmp"зробити це щасливим.
peelman

3
Важливо відзначити, що * є важливим. $ ! -path "./directory/*"
Томас Беннетт,

3
Відповідно до сторінок керівництва: "Щоб ігнорувати ціле дерево каталогів, використовуйте, -pruneа не перевіряйте кожен файл у дереві." Якщо ваші виключені каталоги працюють дуже глибоко або містять безліч файлів, і ви дбаєте про продуктивність, тоді скористайтеся цим -pruneваріантом.
thdoan

8

Ось один із способів це зробити ...

find . -type f -name "*_peaks.bed" | egrep -v "^(./tmp/|./scripts/)"

2
Це заслуговує на роботу з будь-якою версією find, а не лише з GNU find. Однак питання має позначку Linux, тому це не є критичним.
Джонатан Леффлер

2

Використовуйте

find \( -path "./tmp" -o -path "./scripts" \) -prune -o  -name "*_peaks.bed" -print

або

find \( -path "./tmp" -o -path "./scripts" \) -prune -false -o  -name "*_peaks.bed"

або

find \( -path "./tmp" -path "./scripts" \) ! -prune -o  -name "*_peaks.bed"

Порядок важливий. Він оцінює зліва направо. Завжди починайте з виключення шляху.

Пояснення

Не використовуйте -not(або !) для виключення цілого каталогу. Використовуйте -prune. Як пояснюється в посібнику:

−prune    The primary shall always evaluate as  true;  it
          shall  cause  find  not  to descend the current
          pathname if it is a directory.  If  the  −depth
          primary  is specified, the −prune primary shall
          have no effect.

а в GNU знайти посібник:

-path pattern
              [...]
              To ignore  a  whole
              directory  tree,  use  -prune rather than checking
              every file in the tree.

Дійсно, якщо ви використовуєте -not -path "./pathname", find обчислює вираз для кожного вузла в "./pathname".

вирази find - це просто оцінка стану.

  • \( \)- групова операція (ви можете використовувати -path "./tmp" -prune -o -path "./scripts" -prune -o, але це більш детально).
  • -path "./script" -prune- якщо -pathповертає true і є каталогом, поверніть true для цього каталогу і не спускайтеся в нього.
  • -path "./script" ! -prune- це оцінює як (-path "./script") AND (! -prune). Це повертає "завжди істинну" чорносливу до завжди помилкової. Це дозволяє уникнути друку "./script"як сірника.
  • -path "./script" -prune -false- оскільки -pruneзавжди повертає true, ви можете слідувати за ним, -falseщоб зробити те саме, що !.
  • -o- АБО оператор. Якщо між двома виразами не вказано жодного оператора, то за замовчуванням використовується оператор І.

Отже, \( -path "./tmp" -o -path "./scripts" \) -prune -o -name "*_peaks.bed" -printрозширено до:

[ (-path "./tmp" OR -path "./script") AND -prune ] OR ( -name "*_peaks.bed" AND print )

Тут друк важливий, оскільки без нього можна розширити:

{ [ (-path "./tmp" OR -path "./script" )  AND -prune ]  OR (-name "*_peaks.bed" ) } AND print

-printдодається за допомогою find - саме тому більшу частину часу вам не потрібно додавати його у свій вираз. І оскільки -pruneповертає true, він надрукує "./script" та "./tmp".

В інших це не потрібно, тому що ми перейшли -pruneна завжди повертати false.

Підказка: Ви можете використовувати, find -D opt expr 2>&1 1>/dev/nullщоб побачити, як його оптимізують та розширюють,
find -D search expr 2>&1 1>/dev/nullщоб побачити, який шлях перевірено.


0

Спробуйте щось на зразок

find . \( -type f -name \*_peaks.bed -print \) -or \( -type d -and \( -name tmp -or -name scripts \) -and -prune \)

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


0

для мене це рішення не працювало з командою exec з find, насправді не знаю чому, тому моє рішення є

find . -type f -path "./a/*" -prune -o -path "./b/*" -prune -o -exec gzip -f -v {} \;

Пояснення: те саме, що і сампсон-чен з доповненнями

-prune - ігнорувати процедурний шлях до ...

-o - Тоді, якщо збіг не надрукує результати, (обріжте каталоги та роздрукуйте решту результатів)

18:12 $ mkdir a b c d e
18:13 $ touch a/1 b/2 c/3 d/4 e/5 e/a e/b
18:13 $ find . -type f -path "./a/*" -prune -o -path "./b/*" -prune -o -exec gzip -f -v {} \;

gzip: . is a directory -- ignored
gzip: ./a is a directory -- ignored
gzip: ./b is a directory -- ignored
gzip: ./c is a directory -- ignored
./c/3:    0.0% -- replaced with ./c/3.gz
gzip: ./d is a directory -- ignored
./d/4:    0.0% -- replaced with ./d/4.gz
gzip: ./e is a directory -- ignored
./e/5:    0.0% -- replaced with ./e/5.gz
./e/a:    0.0% -- replaced with ./e/a.gz
./e/b:    0.0% -- replaced with ./e/b.gz

Прийнята відповідь не спрацювала, але це працює. Використання чорносливу, find . -path ./scripts -prune -name '*_peaks.bed' -type f. Не знаю, як виключити кілька каталогів. Тут також перелічено виключений каталог верхнього рівня, хоча typeвін і вказаний. Виключення через Grep здається більш простим, якщо ви не хочете використовувати чорнослив для прискорення операції пошуку.
Mohnish

У мене були проблеми з виключенням декількох каталогів, але коментарі вище дали мені відповідь, яка спрацювала. Я використовую кілька екземплярів '-not -path', і в кожен вираз шляху включаю повний префікс, який використовується в першому параметрі, щоб 'знайти' і закінчити кожен зірочкою (і уникнути будь-яких крапок).
JetSet

0

Ви можете спробувати нижче:

find ./ ! \( -path ./tmp -prune \) ! \( -path ./scripts -prune \) -type f -name '*_peaks.bed'

2
На таке старе запитання (4 роки!) Ви хочете пояснити, чому ця нова відповідь краща чи інша, а не просто "скинути" код.
Nic3500
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.