Oluşturulan dosya adları listesini bağımsız değişken listesi olarak - boşluklarla kullanma


16

Tarafından toplanan dosya adlarının bir listesini içeren bir komut dosyasını çağırmaya çalışıyorum find. Özel bir şey yok, sadece böyle bir şey:

$ myscript `find . -name something.txt`

Sorun, bazı yol adlarının boşluk içermesidir, bu nedenle bağımsız değişken genişletmesinde iki geçersiz ada ayrılırlar. Normalde isimleri tırnak işaretleri ile sararım, ancak buraya arka tırnak genişletmesi eklenir. Çıktılarını filtrelemeyi denedim findve her dosya adını tırnak işaretleri ile çevreledim, ancak bash onları gördüğünde, onları şeritlemek için çok geç ve dosya adının bir parçası olarak ele alındı:

$ myscript `find . -name something.txt | sed 's/.*/"&"/'`
No such file or directory: '"./somedir/something.txt"'

Evet, komut satırının nasıl işlendiğine ilişkin kurallar bu, ama nasıl aşabilirim?

Bu utanç verici ama doğru yaklaşımı bulamıyorum. Sonunda bunu nasıl yapacağımı anladım xargs -0 -n 10000... ama hala sormak istediğim o kadar çirkin bir hack: Geriye dönük alıntı genişlemesinin sonuçlarını nasıl teklif edebilirim veya aynı etkiyi başka bir şekilde elde edebilirim?

Düzenleme: Ben gerçeği hakkında karıştı xargs yapar aksi söyleneni veya sistem sınırları aşıldı olabilir sürece, tek bir argüman listesine toplamak tüm argümanları. Beni düzleştirdiği için herkese teşekkürler! Diğerleri, kabul edilen cevabı okurken bunu aklınızda bulundurun, çünkü çok doğrudan işaret edilmemiştir.

Cevabı kabul ettim, ama sorum şu: Backtick (veya $(...)) genişlemesindeki boşlukları korumanın bir yolu yok mu? (Kabul edilen çözümün bash olmayan bir cevap olduğuna dikkat edin).


Kabuğun dosya adı ayırıcıları olarak ne kullandığını değiştirmeniz gerekir (örneğin, IFS değeri ile oynayarak, olası bir yol IFS=", yeni satır "). Ancak komut dosyasını tüm dosya adları üzerinde yürütmeye gerek var mı? Değilse, her dosya için komut dosyasını yürütmek üzere find komutunu kullanmayı düşünün.
njsg

IFS'yi değiştirmek harika bir fikir, bunu düşünmemişti! Komut satırı kullanımı için pratik değil, yine de. :-) Ve evet, amaç tüm argümanları senaryomun aynı çağrısına iletmektir.
alexis

Yanıtlar:


12

Bunu findve bunun xargsgibi bazı uygulamaları kullanarak aşağıdakileri yapabilirsiniz .

$ find . -type f -print0 | xargs -r0 ./myscript

veya standart olarak find:

$ find . -type f -exec ./myscript {} +

Misal

Aşağıdaki örnek dizine sahibim diyelim.

$ tree
.
|-- dir1
|   `-- a\ file1.txt
|-- dir2
|   `-- a\ file2.txt
|-- dir3
|   `-- a\ file3.txt
`-- myscript

3 directories, 4 files

Şimdi bunun için elimde olduğunu varsayalım ./myscript.

#!/bin/bash

for i in "$@"; do
    echo "file: $i"
done

Şimdi aşağıdaki komutu çalıştırdığımda.

$ find . -type f -print0 | xargs -r0 ./myscript 
file: ./dir2/a file2.txt
file: ./dir3/a file3.txt
file: ./dir1/a file1.txt
file: ./myscript

Ya da 2. formu böyle kullandığımda:

$ find . -type f -exec ./myscript {} +
file: ./dir2/a file2.txt
file: ./dir3/a file3.txt
file: ./dir1/a file1.txt
file: ./myscript

ayrıntılar

+ xargs bul

Yukarıdaki 2 yöntem, farklı görünseler de, aslında aynıdır. Birincisi, çıktıyı find'dan almak, bulmak \0için -print0anahtar aracılığıyla NULLs ( ) kullanarak bölmek . xargs -0Özellikle en NULL'ları kullanılarak bölünmüş olduğunu girdi almak üzere tasarlanmıştır. Bu standart olmayan sözdizimi GNU tarafından tanıtıldı findve xargsgünümüzde en yeni BSD'ler gibi birkaç diğerinde de bulundu. -rSeçenek arama önlemek için gereklidir myscript, eğer findGNU bulur şey finddeğil BSD ile.

NOT: Bu yaklaşımın tamamı, aşırı uzun bir dizeyi asla geçiremeyeceğinize bağlıdır. Eğer öyleyse, ikinci bir çağırma, ./myscriptbulmanın sonraki sonuçlarının geri kalanıyla başlayacaktır.

+ ile bul

Bu standart yoldur (ancak GNU uygulamasına nispeten yakın zamanda eklenmiş olsa da (2005) find). Yaptığımız şeyi yapma yeteneği xargstam anlamıyla yerleşiktir find. Yani finddosyaların bir listesini bulacaksınız ve sonrasında belirlenen komuta sığabilecek kadar sonra birçok argüman olarak o listeyi geçmesi -exec(not {}sadece önceki son olabilir +bu durumda) gerekirse komutları birkaç kez koşarak.

Neden teklif yok?

İlk örnekte, bağımsız değişkenleri ayırmak için NULL'ları kullanarak alıntı ile ilgili sorunlardan tamamen kaçınarak bir kısayol alıyoruz. Ne zaman xargsbu listeyi verilir etkili bireysel komut atomlarının korunmaları boş değerlere üzerinde bölünmüş talimatı var.

İkinci örnekte, sonuçları dahili findtutuyoruz ve bu nedenle her bir dosya atomunun ne olduğunu biliyor ve bunları uygun şekilde ele almayı garanti edecek, böylece bunları alıntılamaktan kaçınacağız.

Maksimum komut satırı boyutu?

Bu soru zaman zaman ortaya çıkıyor, bu yüzden bir bonus olarak bu cevaba ekliyorum, esas olarak gelecekte bulabiliyorum. Ortamın xargssınırının nasıl olduğunu görmek için kullanabilirsiniz :

$ xargs --show-limits
Your environment variables take up 4791 bytes
POSIX upper limit on argument length (this system): 2090313
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2085522
Size of command buffer we are actually using: 131072

1
Teşekkürler, ancak tüm argümanları senaryomun aynı çağrısına geçirmem gerekiyor . Sorun açıklamasında bu, ama sanırım tesadüfi olmadığını açıkça belirtmedim.
alexis

@alexis - cevapları tekrar okuyun, tüm argümanları senaryonuzun tek bir çağrısına geçirirler.
slm

Lanetleneceğim! Ben +argüman hakkında bilmiyordum find(ve sen +de nesir kullanın , bu yüzden ilk kez açıklamayı kaçırdım). Ama daha da önemlisi, xargsvarsayılan olarak ne yapar yanlış anladım !!! Unix'i otuz yıl kullandığımda şimdiye kadar hiç kullanmadım, ama alet
kutumu

@alexis - Söylediklerimizi kaçıracağını düşündüm. Evet xargs, bir emrin şeytanıdır. findOkumalısınız ve erkeğin yaptıklarını grok yapmak için defalarca sayfalar açmalısınız. Mayıs anahtarları birbirlerinin kontrendikedir, böylece karışıklığa katkıda bulunur.
slm

@alexis - araç kutusuna eklemek için bir şey daha, iç içe geçmiş komutları çalıştırmak için backquotes / backticks kullanmayın, $(..)bunun yerine şimdi kullanın. Otomatik olarak tırnak vb. İç içe işler. Backticks kullanımdan kaldırılıyor.
slm

3
find . -name something.txt -exec myscript {} +

Yukarıda, findeşleşen tüm dosya adlarını bulur ve bunları bağımsız değişken olarak sağlar myscript. Bu, boşluklardan veya diğer garip karakterlerden bağımsız olarak dosya adlarıyla çalışır.

Tüm dosya adları bir satıra sığarsa, myscript bir kez yürütülür. Liste, kabuğun işleyemeyeceği kadar uzunsa find, myscript'i gerektiğinde birden çok kez çalıştırır.

Daha fazla: Bir komut satırına kaç dosya sığar? komut satırlarını "xargs'ın oluşturduğu gibi" man findoluşturduğunu söylüyor find. Ve man xargssınırların sisteme bağlı olduğunu ve bunları çalıştırarak belirleyebildiğinizi xargs --show-limits. ( getconf ARG_MAXaynı zamanda bir olasılıktır). Linux'ta sınır genellikle (her zaman değil) komut satırı başına yaklaşık 2 milyon karakterdir.


2

@ Slm'in iyi cevabına birkaç ekleme

Bağımsız değişkenlerin boyutuyla ilgili sınırlama execve(2)sistem çağrısındadır (aslında, bağımsız değişkenin ve ortam dizelerinin ve işaretçilerin kümülatif boyutundadır). Eğer myscriptsizin kabuk yorumlayabilir bir dilde yazılmış, o zaman belki gerekmez yürütmek Eğer kabuk sadece başka tercüman yürütmek zorunda kalmadan yorumlamak olabilir, bunu.

Komut dosyasını şu şekilde çalıştırırsanız:

(. myscript x y)

Gibi:

myscript x y

Geçerli kabuğun bir alt öğesi tarafından yürütmek yerine yorumlanması dışında (eninde sonunda yürütmeyi sh (veya varsa o-bang satırını belirtiyorsa ) daha fazla argümanla).

Şimdi belli ki kullanamazsınız find -exec {} +ile .komuta gibi .kabuğun bir yerleşik komutu olmak, değil tarafından, kabuk tarafından yürütülecek vardır find.

İle zsh, kolay:

IFS=$'\0'
(. myscript $(find ... -print0))

Veya:

(. myscript ${(ps:\0:)"$(find ... -print0)"}

Bununla birlikte zsh, findözelliklerinin çoğu zshglobbing içine yerleştirildiğinden, ilk etapta ihtiyacınız olmaz .

bashancak değişkenler NUL karakter içeremez, bu nedenle başka bir yol bulmanız gerekir. Bunun bir yolu olabilir:

files=()
while IFS= read -rd '' -u3 file; do
  files+=("$file")
done 3< <(find ... -print0)
(. myscript "${files[@]}")

Ayrıca globstar, bash4.0 ve sonraki sürümlerde zsh tarzı özyinelemeli globbing de kullanabilirsiniz :

shopt -s globstar failglob dotglob
(. myscript ./**/something.txt)

4.3'te **düzeltilinceye kadar dizinlerin takip edildiğini unutmayın bash. Ayrıca bash, zshglobbing niteleyicileri uygulamadığından , findoradaki tüm özellikleri elde edemeyeceğinizi unutmayın .

Başka bir alternatif GNU kullanmak olacaktır ls:

eval "files=(find ... -exec ls -d --quoting-style=shell-always {} +)"
(. myscript "${files[@]}")

Emin olmak istiyorsanız, yukarıdaki yöntemler de kullanılabilir myscriptolduğu infaz (argüman listesi çok büyükse kalınan) sadece bir kez. Linux'un son sürümlerinde, argüman listesindeki bu sınırlamayı aşağıdakilerle artırabilir ve hatta kaldırabilirsiniz:

ulimit -s 1048576

(1GiB yığın boyutu, dörtte biri arg + env listesi için kullanılabilir).

ulimit -s unlimited

(limit yok)


1

Çoğu sistemde, xargsveya kullanılarak herhangi bir programa aktarılan bir komut satırının uzunluğunda bir sınır vardır -exec command {} +. Gönderen man find:

-exec command {} +
      This  variant  of the -exec action runs the specified command on
      the selected files, but the command line is built  by  appending
      each  selected file name at the end; the total number of invoca
      tions of the command will  be  much  less  than  the  number  of
      matched  files.   The command line is built in much the same way
      that xargs builds its command lines.  Only one instance of  `{}'
      is  allowed  within the command.  The command is executed in the
      starting directory.

Çağrılar çok daha az olacak, ancak bir tane olacağı garanti edilmeyecek. Yapmanız gereken, komut dosyasındaki NUL ile ayrılmış dosya adlarını stdin'den okumak, bir komut satırı argümanına dayanmaktır -o -. Gibi bir şey yapardı:

$ find . -name something.txt -print0 | myscript -0 -o -

ve seçenek argümanlarını myscriptbuna göre uygulayın.


Evet, işletim sistemi iletilebilecek bağımsız değişkenlerin sayısı / boyutu için bir sınır belirler. Modern Linux sistemlerinde bu (devasa) ( linux.die.net/man/2/execve ) (yığın boyutunun 1 / 4'ü, 0x7FFFFFFF argümanları). AFAIK bash'ın kendisi herhangi bir sınır koymaz. Listelerim çok daha küçük ve sorunum, yanlış anlama veya nasıl xargsçalıştığını yanlış hatırlamadan kaynaklandı . Çözümünüz gerçekten en sağlam, ancak bu durumda aşırıya kaçıyor.
alexis

0

Backtick (veya $ (...)) genişlemesindeki boşlukları korumanın bir yolu yok mu?

Hayır, yok. Neden?

Bash'in neyin korunması gerektiğini ve nelerin olmaması gerektiğini bilmesinin bir yolu yoktur.

Unix dosyasında / kanalında diziler yok. Sadece bir bayt akışı. ``Veya içindeki komut $(), bash yutar ve tek bir dize gibi davranan bir akış çıkarır. Bu noktada, sadece iki seçeneğiniz var: onu tek bir dize olarak tutmak için tırnak içine alın veya çıplak yapın, böylece bash yapılandırılmış davranışına göre böler.

Öyleyse, bir dizi istiyorsanız, bir dizi içeren bir bayt formatı tanımlamak xargsve findyapmanız gereken ve aşağıdaki gibi araçların yapmak zorunda olduğunuz şeydir : Bunları -0argüman ile çalıştırırsanız , öğeleri sonlandıran ikili bir dizi formatına göre çalışırlar. null bayt, aksi takdirde opak bayt akışına semantik ekler.

Ne yazık ki, bashboş bayttaki dizeleri ayıracak şekilde yapılandırılamaz. Bize bunu yapabildikleri için /unix//a/110108/17980 adresine teşekkürler zsh.

xargs

Komutunuzun bir kez çalışmasını istiyorsunuz ve bunun xargs -0 -n 10000sorununuzu çözdüğünü söylediniz . Bu, 10000'den fazla parametreniz varsa, komutunuzun bir kereden fazla çalışmasını sağlar.

Kesinlikle bir kez çalışmasını veya başarısız olmasını istiyorsanız, -xargümanı ve -nargümandan daha büyük bir argümanı sağlamanız gerekir -s(gerçekten: bir grup sıfır uzunluklu argüman artı komutun isminin sığmaması için yeterince büyük -sboyutu). ( adam xargs , aşağıdan alıntıya bakınız)

Şu anda bulunduğum sistemin 8M ile sınırlı bir yığını var, işte benim sınırım:

$ printf '%s\0' -- {1..1302582} | xargs -x0n 2076858 -s 2076858 /bin/true
xargs: argument list too long
$ printf '%s\0' -- {1..1302581} | xargs -x0n 2076858 -s 2076858 /bin/true
(no output)

darbe

Harici bir komut eklemek istemiyorsanız, /unix//a/110108/17980 adresinde gösterildiği gibi bir diziyi beslerken while-read döngüsü, bash'ın şeyleri bölmesinin tek yoludur. boş bayt.

( . ... "$@" )Yığın boyutu sınırını önlemek için komut dosyasını kaynak fikri harika (denedim, işe yarıyor!), Ancak normal durumlar için muhtemelen önemli değil.

Stdin'den başka bir şey okumak istiyorsanız, işlem borusu için özel bir fd kullanmak önemlidir, ancak aksi takdirde buna ihtiyacınız olmaz.

Yani, günlük ev ihtiyaçları için en basit "yerel" yol:

files=()
while IFS= read -rd '' file; do
    files+=("$file")
done <(find ... -print0)

myscriptornonscript "${files[@]}"

İşlem ağacınızı temiz ve güzel görünmesini isterseniz, bu yöntem exec mynonscript "${files[@]}", bash işlemini bellekten kaldırır ve çağrılan komutla değiştirir. xargskomut yalnızca bir kez çalışsa bile, çağrılan komut çalışırken her zaman bellekte kalır.


Yerel bash yöntemine karşı konuşan şey şudur:

$ time { printf '%s\0' -- {1..1302581} | xargs -x0n 2076858 -s 2076858 /bin/true; }

real    0m2.014s
user    0m2.008s
sys     0m0.172s

$ time {
  args=()
  while IFS= read -rd '' arg; do
    args+=( "$arg" )
  done < <(printf '%s\0' -- $(echo {1..1302581}))
  /bin/true "${args[@]}"
}
bash: /bin/true: Argument list too long

real    107m51.876s
user    107m38.532s
sys     0m7.940s

bash dizi işleme için optimize edilmemiştir.


adam xargs :

-n max-args

Komut satırı başına en fazla max-args argümanı kullanın. -X seçeneği belirtilmedikçe, boyut (-s seçeneğine bakın) aşılırsa, max-args değerinden daha az bağımsız değişken kullanılır; bu durumda xargs çıkar.

-s max-chars

Komut ve ilk bağımsız değişkenler ve bağımsız değişken dizelerinin sonlarındaki sonlandırma null'ları dahil olmak üzere komut satırı başına en fazla max karakter karakteri kullanın. İzin verilen en büyük değer sisteme bağlıdır ve exec için bağımsız değişken uzunluğu sınırı, ortamınızın boyutu daha az, 2048 baytlık daha az boş alan olarak hesaplanır. Bu değer 128 KB'den fazlaysa, varsayılan değer olarak 128Kib kullanılır; aksi takdirde, varsayılan değer maksimum değerdir. 1 KB 1024 bayttır.

-x

Boyut aşılırsa (-s seçeneğine bakın) çıkın.


Tüm sorun için teşekkürler ama temel öncülünüz bash normalde ayrıntılı bir teklif işleme sistemi kullandığını görmezden gelir . Ancak, backquote genişlemesinde değil. Aşağıdakileri karşılaştırın (vermek hem hataları, ancak farkı göstermek): ls "what is this"vs ls `echo '"what is this"'` . Birisi backquotes sonucu için teklif işleme uygulamayı ihmal etti.
alexis

Backquotes teklif işleme yapmak sevindim. Kelime bölme bile yapmaları modern bilgi işlem geçmişinde yeterince karışık görünüme, kafa çizilmeye ve güvenlik hatalarına neden oldu.
clacke

Soru "Backtick (veya $(...)) genişlemesindeki boşlukları korumanın bir yolu yok mu?"
clacke

Boş sonlandırılmış öğe dizisi biçimi, bir diziyi ifade etmenin en basit ve en güvenli yoludur. bashGörünüşe göre doğal olarak desteklemeyen bir utanç zsh.
clacke

Aslında, sadece bu hafta kullandım printf "%s\0"ve xargs -0bir ara aracın bir kabuk tarafından ayrıştırılan bir dize üzerinden parametreleri geçeceği bir tırnak durumu etrafında dolaşmak için. Alıntı yapmak her zaman sizi ısırmaya geri döner.
clacke
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.