-exec Linux'ta bir kabuk işlevi bulmak?


185

findKabukta tanımladığım bir işlevi yürütmenin bir yolu var mı ? Örneğin:

dosomething () {
  echo "doing something with $1"
}
find . -exec dosomething {} \;

Bunun sonucu:

find: dosomething: No such file or directory

Almanın bir yolu var mı find's -execgörmeye dosomething?

Yanıtlar:


262

Yalnızca kabuk, kabuk işlevlerinin nasıl çalıştırılacağını bildiğinden, bir işlevi çalıştırmak için bir kabuk çalıştırmanız gerekir. Ayrıca, dışa aktarma işlevinizi işaretlemeniz gerekir export -f, aksi takdirde alt kabuk bunları devralamaz:

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;

7
Beni yendin. Bu arada, kaşlı ayraçları kullanmak yerine tırnak içine alabilirsiniz $0.
sonraki duyuruya kadar duraklatıldı.

20
Eğer kullanırsanız find . -exec bash -c 'dosomething "$0"' {} \;o Dosya adlarında boşluk (ve diğer garip karakter) idare edeceğiz ...
Gordon Davisson

3
@alxndr: çift tırnak, geri tırnak, dolar işareti, bazı kaçış kombinasyonları, vb. ile dosya adlarında başarısız olur ...
Gordon Davisson

4
Ayrıca, -f öğelerini de dışa aktarmazsanız , işlevinizin çağırabileceği işlevlerin kullanılamayacağını unutmayın.
hraban

3
Bu export -fsadece bash'ın bazı sürümlerinde çalışır. /bin/sh
Posix

120
find . | while read file; do dosomething "$file"; done

10
Güzel çözüm. Fonksiyonu dışa aktarmayı veya kaçan argümanları karıştırmayı gerektirmez ve her bir işlevi yürütmek için alt kabukları üretmediği için muhtemelen daha etkilidir.
Tom

19
Bununla birlikte, yeni satırlar içeren dosya adlarını kıracağını unutmayın.
chepner

3
Global değişkenleriniz ve işlevleriniz her seferinde tamamen yeni bir kabuk / ortam oluşturmadan kullanılabileceğinden bu daha "kabuk'tur". Adam'ın yöntemini denedikten ve her türlü çevre sorunuyla karşılaştıktan sonra bunu zor yoldan öğrendim. Bu yöntem aynı zamanda mevcut kullanıcınızın kabuğunu tüm ihracatlarla bozmaz ve daha az disiplin gerektirir.
Ray Foss

Hiçbir alt kabuk ile kabul edilen cevap çok daha hızlı! GÜZEL! Teşekkür ederim!
Bill Heller

2
Ayrıca while readbir for döngüsü için değiştirerek sorunumu giderdi; for item in $(find . ); do some_function "${item}"; done
user5359531

22

Jak'ın yukarıdaki cevabı harika ama kolayca üstesinden gelebilecek birkaç tuzak var:

find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done

Bu, satır beslemesi yerine sınırlayıcı olarak null değerini kullanır, bu nedenle satır beslemeli dosya adları çalışır. Ayrıca -r, dosya adlarındaki ters eğik çizgiler çalışmadığı sürece ters eğik çizgi çıkışını devre dışı bırakan bayrağı kullanır . Ayrıca, adlardaki IFSpotansiyel sondaki boşluk alanlarının atılmaması için temizlenir .


1
İçin iyi /bin/bashama işe yaramayacak /bin/sh. Ne yazık.
Роман Коптев

@ РоманКоптев En azından / bin / bash içinde çalıştığı için ne kadar şanslı.
sdenham

21

Alıntıları {}aşağıda gösterildiği gibi ekleyin :

export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;

Bu find, örneğin adlarında parantez bulunan dosyalar tarafından döndürülen özel karakterler nedeniyle oluşan hataları düzeltir .


1
Bu doğru kullanım şekli değildir {}. Bu, çift tırnak içeren bir dosya adı için kırılır. touch '"; rm -rf .; echo "I deleted all you files, haha. Hata.
gniourf_gniourf

Evet, bu çok kötü. Enjeksiyonlarla sömürülebilir. Çok güvensiz. Bunu kullanmayın!
Dominik

1
@kdubs: Kullanım $ 0 komut dizesi içinde (tırnak içine) ve birinci argüman olarak dosya adını geçmesi: -exec bash -c 'echo $0' '{}' \;kullanılırken Not olduğunu bash -c, $ 0 İlk argüman değil, komut adıdır.
sdenham

10

Sonuçları toplu olarak işleme

Artan verimlilik için, birçok kişi xargssonuçları toplu olarak işlemek için kullanır , ancak çok tehlikelidir. Bu nedenle, findsonuçları toplu olarak yürüten alternatif bir yöntem vardı .

Bununla birlikte, bu yöntemin komutun sonunda findolması gereken POSIX'te bir gereksinim gibi bazı uyarılarla gelebileceğini unutmayın {}.

export -f dosomething
find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +

findbirçok sonucu tek bir çağrıya argüman olarak iletir bashve for-loop bu bağımsız değişkenler aracılığıyla yineleyerek işlevi dosomethingbunların her birinde gerçekleştirir.

Yukarıdaki çözüm şu argümanlarda başlar $1, bu yüzden _(temsil eden $0) vardır.

Sonuçları birer birer işlemek

Aynı şekilde, kabul edilen üst cevabın da

export -f dosomething
find . -exec bash -c 'dosomething "$1"' _ {} \;

Bu sadece daha aklı başında değildir, çünkü argümanlar her zaman başlamalıdır $1, ancak $0döndürülen dosya adının findkabuğa özel bir anlamı varsa kullanmak beklenmedik davranışlara yol açabilir .


9

Bağımsız değişken olarak bulunan her öğeyi ileterek komut dosyasının kendisini aramasını sağlayın:

#!/bin/bash

if [ ! $1 == "" ] ; then
   echo "doing something with $1"
   exit 0
fi

find . -exec $0 {} \;

exit 0

Komut dosyasını tek başına çalıştırdığınızda, aradığınızı bulur ve kendisini her bir buluntu sonucunu argüman olarak iletir. Komut dosyası bir bağımsız değişkenle çalıştırıldığında, bağımsız değişken üzerindeki komutları yürütür ve sonra çıkar.


harika fikir ama kötü stil: aynı komut dosyasını iki amaç için kullanır. bölmenizdeki dosya sayısını azaltmak istiyorsanız / tüm komut dosyalarınızı başlangıçta büyük bir cümle maddesi olan tek bir dosyada birleştirebilirsiniz. çok temiz bir çözüm, değil mi?
user829755

Bu olarak başlar find: ‘myscript.sh’: No such file or directoryeğer başarısız olur bahsetmiyorum bash myscript.sh...
Camusensei

5

Geçerli dizindeki tüm dosyalarda belirli bir komutu yürütecek bir bash işlevi arayanlar için, yukarıdaki cevaplardan birini derledim:

toall(){
    find . -type f | while read file; do "$1" "$file"; done
}

Boşluk içeren dosya adlarıyla bozulduğunu unutmayın (aşağıya bakın).

Örnek olarak, bu işlevi alın:

world(){
    sed -i 's_hello_world_g' "$1"
}

Geçerli dizindeki tüm dosyalarda tüm merhaba örneklerini dünyaya değiştirmek istediğimi varsayalım. Yapardım:

toall world

Dosya adlarındaki herhangi bir sembolle güvende olmak için şunu kullanın:

toall(){
    find . -type f -print0 | while IFS= read -r -d '' file; do "$1" "$file"; done
}

(ancak GNU gibi bir findişleme ihtiyacınız vardır ).-print0find


3

Tek komutta iki komutu tekrarlayarak en kolay yolu aşağıdaki gibi buluyorum

func_one () {
  echo "first thing with $1"
}

func_two () {
  echo "second thing with $1"
}

find . -type f | while read file; do func_one $file; func_two $file; done

3

Başvuru için, ben bu senaryo kullanarak kaçının:

for i in $(find $dir -type f -name "$name" -exec ls {} \;); do
  _script_function_call $i;
done;

Mevcut komut dosyasında find çıktısını alın ve çıktıyı istediğiniz gibi tekrarlayın. Kabul edilen yanıtı kabul ediyorum, ancak komut dosyası dışında işlevi göstermek istemiyorum.


boyut sınırları vardır
Richard

2

Bir işlevi bu şekilde yürütmek mümkün değildir .

Bunun üstesinden gelmek için işlevinizi bir kabuk betiğine yerleştirebilir ve find

# dosomething.sh
dosomething () {
  echo "doing something with $1"
}
dosomething $1

Şimdi bulmak için kullanın:

find . -exec dosomething.sh {} \;

~ / Bin dosyamda daha fazla dosyadan kaçınmaya çalışıyordu. Yine de teşekkürler!
alxndr

Aşağı oylamayı düşündüm ama çözüm kendi içinde kötü değil. Lütfen doğru dosomething $1dosomething "$1"find . -exec bash dosomething.sh {} \;
alıntıyı

2

İşlevi ayrı bir dosyaya koyun ve findçalıştırın.

Kabuk işlevleri tanımlandıkları kabuğun içindedir; findonları asla göremez.


Anladım; mantıklı. Ama benim ~ / bin daha fazla dosya önlemek için çalışıyordu.
alxndr

2

execVeya execdir( -exec command {} +) için toplu seçeneği kullanıyorsanız ve tüm konumsal bağımsız değişkenleri almak istiyorsanız , diğer yanıtların bazılarına ekleme ve açıklama sağlamak için , $0ile işlemeyi göz önünde bulundurmanız gerekir bash -c. Daha somut olarak, bash -cyukarıda önerildiği gibi kullanılan aşağıdaki komutu göz önünde bulundurun ve bulduğu her dizinden '.wav' ile biten dosya yollarını yankılar:

find "$1" -name '*.wav' -execdir bash -c 'echo $@' _ {} +

Bash el kitabı diyor ki:

If the -c option is present, then commands are read from the first non-option argument command_string.  If there are arguments after the command_string, they  are  assigned  to  the
                 positional parameters, starting with $0.

İşte, 'check $@'komut dizesi ve _ {}komut dizesinden sonraki argümanlar. Bunun $@bash içinde 1'den başlayan tüm konum parametrelerine genişleyen özel bir konum parametresi olduğunu unutmayın . Ayrıca -cseçenekle, ilk bağımsız değişkenin konum parametresine atandığını unutmayın $0. Bu, ile tüm konumsal parametrelere erişmeye çalışırsanız $@, yalnızca başlangıç $1ve bitiş parametrelerini alacaksınız demektir . Dominik'in cevabının, _parametreyi doldurmak için kukla bir argüman olan nedeni budur, bu nedenle , örneğin parametre genişletmeyi veya bu yanıttaki gibi for döngüsünü $0kullanırsak, istediğimiz tüm argümanlar daha sonra kullanılabilir $@.

Tabii ki, kabul edilen cevaba benzer şekilde, bash -c 'shell_function $0 $@'açıkça geçerek de işe yarayacaktır $0, ancak yine de, $@beklendiği gibi çalışmayacağını aklınızda bulundurmanız gerekir .


0

Doğrudan değil, hayır. Bul, kabuğunuzda değil ayrı bir işlemde yürütülüyor.

İşlevinizle aynı işi yapan bir kabuk komut dosyası oluşturun ve bunu yapabilirsiniz -exec.


~ / Bin dosyamda daha fazla dosyadan kaçınmaya çalışıyordu. Yine de teşekkürler!
alxndr

-2

-execTamamen kullanmaktan kaçınırdım. Neden kullanmıyorsunuz xargs?

find . -name <script/command you're searching for> | xargs bash -c

O sırada, IIRC kullanılan kaynak miktarını azaltma girişimindeydi. Milyonlarca boş dosyayı bulmayı ve silmeyi düşünün.
alxndr
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.