"Bul" komutu sonuçlarını Bash'te bir dizi olarak nasıl saklayabilirim?


92

findSonucu diziler olarak kaydetmeye çalışıyorum . İşte kodum:

#!/bin/bash

echo "input : "
read input

echo "searching file with this pattern '${input}' under present directory"
array=`find . -name ${input}`

len=${#array[*]}
echo "found : ${len}"

i=0

while [ $i -lt $len ]
do
echo ${array[$i]}
let i++
done

Mevcut dizin altında 2 .txt dosyası alıyorum. Bu yüzden sonucu '2' bekliyorum ${len}. Ancak, 1 yazdırır. Nedeni, tüm sonucu findtek bir öğe olarak almasıdır . Bunu nasıl düzeltebilirim?

Not
: StackOverFlow'da benzer bir sorunla ilgili birkaç çözüm buldum. Ancak, biraz farklı oldukları için benim durumuma başvuramıyorum. Sonuçları döngüden önce bir değişkende saklamam gerekiyor. Tekrar teşekkürler.

Yanıtlar:


133

Linux Kullanıcıları için 2020 Güncellemesi:

Eğer Linux'taysanız muhtemelen yaptığınız gibi güncel bir bash sürümüne (4.4-alpha veya daha iyisi) sahipseniz, Benjamin W.'nin cevabını kullanmalısınız .

Son kontrol ettiğimde hala bash 3.2 kullanan veya daha eski bir bash kullanan Mac OS kullanıyorsanız, sonraki bölüme geçin.

Bash 4.3 veya öncesi için yanıt

Çıktıyı findbir bashdiziye almak için bir çözüm :

array=()
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done < <(find . -name "${input}" -print0)

Bu aldatıcıdır, çünkü genel olarak dosya adlarında boşluklar, yeni satırlar ve diğer komut dosyası düşman karakterleri olabilir. findDosya adlarını kullanmanın ve güvenli bir şekilde birbirinden ayırmanın tek yolu -print0, dosya adlarını bir boş karakterle ayrılmış olarak yazdıran kullanmaktır . Bash'in readarray/ mapfileişlevlerinin boş değerlerle ayrılmış dizgeleri desteklemesi ancak desteklememesi halinde bu pek bir rahatsızlık olmaz. Bash readyapar ve bu bizi yukarıdaki döngüye götürür.

[Bu yanıt ilk olarak 2014'te yazılmıştır. Bash'in yeni bir sürümüne sahipseniz, lütfen aşağıdaki güncellemeye bakın.]

Nasıl çalışır

  1. İlk satır boş bir dizi oluşturur: array=()

  2. İfade her readyürütüldüğünde, standart girdiden boş değerle ayrılmış bir dosya adı okunur. -rSeçenek söyler readyalnız ters eğik çizgi karakterleri bırakmak. , -d $'\0'Girdinin readnull ile ayrıldığını söyler . Biz adını ihmal yana read, kabuk varsayılan adları içine giriş koyar: REPLY.

  3. array+=("$REPLY")İfadesi diziye yeni bir dosya adı ekler array.

  4. Son satır find, whiledöngünün standart girdisinin çıktısını sağlamak için yeniden yönlendirme ve komut ikamesini birleştirir .

Neden süreç ikamesi kullanılıyor?

Süreç ikamesi kullanmadıysak, döngü şu şekilde yazılabilir:

array=()
find . -name "${input}" -print0 >tmpfile
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done <tmpfile
rm -f tmpfile

Yukarıda findöğesinin çıktısı geçici bir dosyada saklanır ve bu dosya while döngüsüne standart girdi olarak kullanılır. Süreç ikame fikri, bu tür geçici dosyaları gereksiz kılmaktır. Böylece, whiledöngünün stdinini alması yerine, onun stdinini almasını tmpfilesağlayabiliriz <(find . -name ${input} -print0).

Süreç ikamesi oldukça faydalıdır. Bir komutun bir dosyadan okumak istediği birçok yerde <(...), dosya adı yerine işlem ikamesi belirtebilirsiniz . >(...)Komutun dosyaya yazmak istediği bir dosya adı yerine kullanılabilen benzer bir form vardır .

Diziler gibi, işlem ikamesi de bash ve diğer gelişmiş kabukların bir özelliğidir. POSIX standardının bir parçası değildir.

Alternatif: lastpipe

İstenirse lastpipeproses ikamesi yerine kullanılabilir (şapka ipucu: Sezar ):

set +m
shopt -s lastpipe
array=()
find . -name "${input}" -print0 | while IFS=  read -r -d $'\0'; do array+=("$REPLY"); done; declare -p array

shopt -s lastpipebash'a mevcut kabuktaki boru hattındaki son komutu çalıştırmasını söyler (arka planda değil). Bu şekilde, arrayboru hattı tamamlandıktan sonra var olan kalıntılar. Çünkü lastpipesadece iş kontrolü kapatılırsa etki eder, koşarız set +m. (Bir komut dosyasında, komut satırının aksine, iş kontrolü varsayılan olarak kapalıdır.)

Ek Notlar

Aşağıdaki komut, bir kabuk dizisi değil, bir kabuk değişkeni oluşturur:

array=`find . -name "${input}"`

Bir dizi oluşturmak istiyorsanız, find'ın çıktısının etrafına parantez koymanız gerekir. Yani, safça, biri şunları yapabilir:

array=(`find . -name "${input}"`)  # don't do this

Sorun, kabuğun sonuçları üzerinde kelime bölme yapmasıdır, findböylece dizinin elemanlarının istediğiniz gibi olacağı garanti edilmez.

2019 Güncellemesi

4.4-alfa sürümünden başlayarak, bash artık -dyukarıdaki döngüye artık gerek kalmaması için bir seçeneği desteklemektedir . Bunun yerine şu kullanılabilir:

mapfile -d $'\0' array < <(find . -name "${input}" -print0)

Bununla ilgili daha fazla bilgi için lütfen Benjamin W.'nin cevabına bakın (ve oy verin) .


1
@JuneyoungOh Yardımcı olduğuna sevindim. Süreç değiştirme bölümü ekledim.
John1024

3
@Rockallite Bu iyi bir gözlem ama eksik. Birden çok kelimeye ayrılmadığımız doğru olsa da, IFS=giriş satırlarının başından veya sonundan boşlukların kaldırılmasından kaçınmamız gerekir . Bunu, 'nın çıktısını read var <<<' abc '; echo ">$var<"ile çıktısını karşılaştırarak kolayca test edebilirsiniz IFS= read var <<<' abc '; echo ">$var<". İlk durumda, önceki ve sonraki boşluklar abckaldırılır. İkincisi, değiller. Boşluk ile başlayan veya biten dosya adları olağandışı olabilir, ancak bunlar var, doğru işlenmelerini istiyoruz.
John1024

1
Ben senin kodu çalıştırmak sonra Merhaba, ben yakın mesaj sözdizimi hatası alıyorum Beklenmeyen belirteç <' yapılır << '(aaa / -not -newermt "$ last_build_timestamp_v" tipi f -print0 bulmak)
Przemysław Sienkiewicz

1
Bir not: basit olan ''yerine kullanılabilir $'\0':n=0; while IFS= read -r -d '' line || [ "$line" ]; do echo "$((++n)):$line"; done < <(printf 'first\nstill first\0second\0third')
glenn jackman

1
@theeagle Yazmak istediğinizi varsayıyorum BLAH=$(find . -name '*.php'). Cevapta tartışıldığı gibi, bu yaklaşım sınırlı durumlarda işe yarayacaktır, ancak genel olarak tüm dosya adlarıyla çalışmayacaktır ve OP'nin beklediği gibi bir dizi üretmez .
John1024

35

Bash 4.4 -d, readarray/ için bir seçenek sundu mapfile, bu nedenle bu artık çözülebilir

readarray -d '' array < <(find . -name "$input" -print0)

boşluklar, satırsonları ve genelleme karakterleri dahil olmak üzere rastgele dosya adlarıyla çalışan bir yöntem için. Bu, örneğin GNU'nun yaptığı gibi , finddesteklerinizin olmasını gerektirir -print0.

Gönderen kılavuzun (diğer seçenekleri atlayarak):

mapfile [-d delim] [array]

-d
İlk karakteri delimsatırsonu yerine her girdi satırını sonlandırmak için kullanılır. Eğer delimboş bir dize olduğunda, mapfilebir NULL karakteri okuduğunda bir çizgi sona erer.

Ve readarraysadece eşanlamlıdır mapfile.


18

bash4 veya sonraki bir sürümü kullanıyorsanız , kullanımınızı findile değiştirebilirsiniz.

shopt -s globstar nullglob
array=( **/*"$input"* )

**Desen olarak etkin globstardesen geçerli dizinde keyfi bir derinliğe maç için izin kibrit 0 veya daha fazla dizinleri. nullglobSeçenek olmadan, desen (parametre genişletmeden sonra) tam anlamıyla ele alınır, bu nedenle hiçbir eşleşme olmadan boş bir dizi yerine tek bir dizeye sahip bir diziniz olur.

dotglobGizli dizinler arasında gezinmek (gibi .ssh) ve gizli dosyaları (gibi .bashrc) eşleştirmek istiyorsanız seçeneği ilk satıra da ekleyin .


4
Belki nullglobde ...
kojiro

1
Evet, bunu hep unuturum.
chepner

5
Bunun dotglob, ayarlanmadıkça gizli dosyaları ve dizinleri içermeyeceğini unutmayın (bu istenebilir veya istenmeyebilir, ancak bahsetmeye değer).
gniourf_gniourf

10

gibi bir şey deneyebilirsin

array=(`find . -type f | sort -r | head -2`)
ve dizi değerlerini yazdırmak için echo gibi bir şey deneyebilirsiniz. "${array[*]}"


8
Boşluklu veya glob karakterli dosya adları varsa kesilir.
gniourf_gniourf

1

Aşağıdakilerin macOS'ta hem Bash hem de Z Shell için çalıştığı görülmektedir.

#! /bin/sh

IFS=$'\n'
paths=($(find . -name "foo"))
unset IFS

printf "%s\n" "${paths[@]}"

Bu, boşluklara ve diğer özel karakterlere sahip dosyalarla çalışır, adlarında satır sonu bulunan dosyalar (kuşkusuz nadirdir) durumunda başarısız olur. Bir test için bir tane oluşturabilirsinizprintf "%b" "file name with spaces, a star * ...\012and a second line\0" | xargs -0 touch
Stéphane Gourichon

-1

Bash'de, $(<any_shell_cmd>)bir komutu çalıştırmaya ve çıktıyı yakalamaya yardımcı olur. Bu Geçme IFSile \nsınırlayıcı bir diziye o dönüştürmek için yardımcı olarak.

IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")

4
Bu, sonuçlarının yalnızca ilk dosyasını finddiziye alacaktır .
Benjamin W.

-2

Bunu beğenebilirsin:

#!/bin/bash
echo "input : "
read input

echo "searching file with this pattern '${input}' under present directory"
array=(`find . -name '*'${input}'*'`)

for i in "${array[@]}"
do :
    echo $i
done

1
Teşekkürler. çok. Ancak @anishsane'in işaret ettiği gibi, programımda dosya adındaki boşluklar dikkate alınmalıdır. Neyse teşekkürler!
Juneyoung Oh

-3

Benim için bu, cygwin'de iyi çalıştı:

declare -a names=$(echo "("; find <path> <other options> -printf '"%p" '; echo ")")
for nm in "${names[@]}"
do
    echo "$nm"
done

Bu, boşluklarla çalışır, ancak dizin adlarında çift tırnak işareti (") kullanmaz (zaten bir Windows ortamında buna izin verilmez).

-Printf seçeneğindeki boşluğa dikkat edin.


3
Bozuk ve tehlikeli : alıntılarla başa çıkmaz ve keyfi kod enjeksiyonuna tabidir. KULLANMAYIN.
gniourf_gniourf

2
Görünüşe göre birisi bu gönderiyi silinmek üzere işaretlemiş. SO'da "yanlış" bir silme nedeni değildir. Kullanıcı cevap vermeye çalıştı, konu üzerine ve cevap kriterlerini karşılıyor. Olumsuz oy düğmesi, silme düğmesi değil, kullanışlılığı ve doğruluğu ölçmek için kullanılır.
Frambot

3
Gniourf'un belirttiği gibi, başkalarının sisteminize seçeneklere girdiği ortamlar için değil, örneğin web sayfaları. Ancak herkes o ortam için program yapmaz. Dizinlerdeki dosyaları yeniden adlandırmak için kullandım.
R Risack
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.