bash: beyazı güvenli seçim yöntemiyle bulmaya seçme


12

Bu dosya adları verildiğinde:

$ ls -1
file
file name
otherfile

bash gömülü boşlukla mükemmel bir şekilde sonuç verir:

$ for file in *; do echo "$file"; done
file
file name
otherfile
$ select file in *; do echo "$file"; done
1) file
2) file name
3) otherfile
#?

Ancak, bazen ben bile kesinlikle her dosya veya çalışmak istemeyebilirsiniz $PWDnerede, findgelir hangi da kolları boşluk ismen.:

$ find -type f -name file\*
./file
./file name
./directory/file
./directory/file name

Ben çıktı almak ve içine sunmak bu scriptlet whispace güvenli bir sürümünü concoct çalışıyorum :findselect

$ select file in $(find -type f -name file); do echo $file; break; done
1) ./file
2) ./directory/file

Ancak, dosya adlarında boşluk ile patlar:

$ select file in $(find -type f -name file\*); do echo $file; break; done
1) ./file        3) name          5) ./directory/file
2) ./file        4) ./directory/file  6) name

Normalde bununla uğraşmak zorunda kalırım IFS. Ancak:

$ IFS=$'\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
$ IFS='\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'

Bunun çözümü nedir?



1
Eğer varsa sadece kullanarak findbelirli bir dosya adıyla eşleşmesi kabiliyeti nedeniyle, sadece kullanabilirsiniz select file in **/file*(ayarladıktan sonra shopt -s globstarcinsinden) bash4 veya üzeri.
chepner

Yanıtlar:


14

Yalnızca boşlukları ve sekmeleri (katıştırılmış yeni satırlar değil) işlemeniz gerekiyorsa, verilen bir diziye okumak için mapfile(veya eşanlamlısını readarray) kullanabilirsiniz.

$ ls -1
file
other file
somefile

sonra

$ IFS= mapfile -t files < <(find . -type f)
$ select f in "${files[@]}"; do ls "$f"; break; done
1) ./file
2) ./somefile
3) ./other file
#? 3
./other file

Eğer varsa bunu tanıtıcı satırsonlarına ihtiyacını ve bashversiyon boş ayrılmış sağlar mapfile1 , o zaman bu değişiklik yapabilirsiniz IFS= mapfile -t -d '' files < <(find . -type f -print0). Aksi takdirde, findbir readdöngü kullanarak null ile ayrılmış çıktıdan eşdeğer bir dizi oluşturun :

$ touch $'filename\nwith\nnewlines'
$ 
$ files=()
$ while IFS= read -r -d '' f; do files+=("$f"); done < <(find . -type f -print0)
$ 
$ select f in "${files[@]}"; do ls "$f"; break; done
1) ./file
2) ./somefile
3) ./other file
4) ./filename
with
newlines
#? 4
./filename?with?newlines

1-d seçeneği eklendi mapfileiçinde bashsürüm 4.4 iirc


2
Daha önce kullanmadığım başka bir fiil için +1
roaima

Gerçekten de, mapfilebenim için de yeni. Kudos.
DopeGhoti

while IFS= readVersiyon (MacOS kullanan bizler için önemlidir) bash v3 geri çalışır.
Gordon Davisson

3
find -print0Varyant için +1 ; homur koyarak için sonra bilinen bir-yanlış sürümü ve tek keşke kullanılmak üzere nitelendirerek bilir onlar yeni satır işlemek gerektiğini. Beklenmedik yerlerde yalnızca beklenmedik biriyle ilgilenirse, hiç beklenmedik bir şeyle uğraşmayacaktır.
Charles Duffy

8

Bu yanıtın her tür dosya için çözümü vardır . Yeni satırlar veya boşluklarla.
Son bash, antik bash ve hatta eski posix kabukları için çözümler var.

Bu cevapta [1] aşağıda listelenen ağaç testler için kullanılır.

seçmek

selectBir dizi ile çalışmak kolaydır :

$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done

Veya konumsal parametrelerle:

$ set -- "$dir"/*
$ select var; do echo "$var"; break; done

Bu nedenle, tek gerçek sorun, bir dizinin içinde veya Konumsal Parametrelerin içinde "dosya listesini" (doğru bir şekilde ayrılmış) almaktır. Okumaya devam et.

darbe

Bash ile bildirdiğiniz sorunu görmüyorum. Bash belirli bir dizinde arama yapabilir:

$ dir='deep/inside/a/dir'
$ printf '<%s>\n' "$dir"/*
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

Veya bir döngüden hoşlanıyorsanız:

$ set -- "$dir"/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

Yukarıdaki sözdiziminin herhangi bir (makul) kabukla (en az csh değil) düzgün çalışacağını unutmayın.

Yukarıdaki sözdiziminin sahip olduğu tek sınır diğer dizinlere inmektir.
Ama bash bunu yapabilirdi:

$ shopt -s globstar
$ set -- "$dir"/**/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

(Bu olanlar gibi sadece bazı dosyaları seçmek için sadece * yerine dosyada):

$ set -- "$dir"/**/*file
$ printf '<%s>\n' "$@"
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/zz last file>

güçlü

Eğer bir "uzay yerleştirdiğinizde kasa başlığında", ne demek istediğini "olduğunu varsaymak gidiyorum sağlam ".

Boşluklara (veya yeni satırlara) karşı sağlam olmanın en basit yolu, boşluklara (veya yeni satırlara) sahip girdilerin işlenmesini reddetmektir. Kabukta bunu yapmanın çok basit bir yolu, herhangi bir dosya adı boşlukla genişlerse hata ile çıkmaktır. Bunu yapmanın birkaç yolu vardır, ancak en kompakt (ve posix) (ancak ani dizin adları ve nokta dosyalarından kaçınmak da dahil olmak üzere bir dizin içeriğiyle sınırlıdır):

$ set -- "$dir"/file*                            # read the directory
$ a="$(printf '%s' "$@" x)"                      # make it a long string
$ [ "$a" = "${a%% *}" ] || echo "exit on space"  # if $a has an space.
$ nl='
'                    # define a new line in the usual posix way.  

$ [ "$a" = "${a%%"$nl"*}" ] || echo "exit on newline"  # if $a has a newline.

Kullanılan çözelti bu maddelerden herhangi birinde sağlamsa testi kaldırın.

Bash'de, alt dizinler yukarıda açıklanan ** ile bir kerede test edilebilir.

Nokta dosyalarını dahil etmenin birkaç yolu vardır, Posix çözümü:

set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*

bulmak

Bulun herhangi bir nedenden dolayı kullanılması gerekiyorsa, ayırıcıyı bir NUL (0x00) ile değiştirin.

bash 4.4+

$ readarray -t -d '' arr < <(find "$dir" -type f -name file\* -print0)
$ printf '<%s>\n' "${arr[@]}"
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/file>

bash 2.05+

i=1  # lets start on 1 so it works also in zsh.
while IFS='' read -d '' val; do 
    arr[i++]="$val";
done < <(find "$dir" -type f -name \*file -print0)
printf '<%s>\n' "${arr[@]}"

POSIXLY

Bulmanın bir NUL sınırlayıcısının bulunmadığı ve okuma için -d(veya -a) bulunmayan geçerli bir POSIX çözümü yapmak için tamamen farklı bir yaklaşım gerekiyor.

-execBulmaktan bir kabuğa çağrı ile bir kompleks kullanmamız gerekir :

find "$dir" -type f -exec sh -c '
    for f do
        echo "<$f>"
    done
    ' sh {} +

Veya, gerekli olan bir seçimse (select sh yerine bash'ın bir parçasıdır):

$ find "$dir" -type f -exec bash -c '
      select f; do echo "<$f>"; break; done ' bash {} +

1) deep/inside/a/dir/file name
2) deep/inside/a/dir/zz last file
3) deep/inside/a/dir/file with a
newline
4) deep/inside/a/dir/directory/file name
5) deep/inside/a/dir/directory/zz last file
6) deep/inside/a/dir/directory/file with a
newline
7) deep/inside/a/dir/directory/file
8) deep/inside/a/dir/file
#? 3
<deep/inside/a/dir/file with a
newline>

[1] Bu ağaç (\ 012 yeni satırlardır):

$ tree
.
└── deep
    └── inside
        └── a
            └── dir
                ├── directory
                   ├── file
                   ├── file name
                   └── file with a \012newline
                ├── file
                ├── file name
                ├── otherfile
                ├── with a\012newline
                └── zz last file

Bu iki komutla oluşturulabilir:

$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}

6

Döngüsel bir yapının önüne bir değişken ayarlayamazsınız, ancak bunu koşulun önüne ayarlayabilirsiniz. İşte man sayfasından segment:

Herhangi bir basit komut veya işlevin ortamı , yukarıda PARAMETERS'da açıklandığı gibi parametre atamalarıyla önek eklenerek geçici olarak artırılabilir.

(Döngü basit bir komut değildir .)

Başarısızlık ve başarı senaryolarını gösteren yaygın olarak kullanılan bir yapı:

IFS=$'\n' while read -r x; do ...; done </tmp/file     # Failure
while IFS=$'\n' read -r x; do ...; done </tmp/file     # Success

Ne yazık ki bir değişti gömmek için bir yol göremiyorum IFSiçine selecto ilişkili bir işlenmesini etkileyen yerken yapı $(...). Ancak, IFSdöngünün dışında ayarlanmasını engelleyecek hiçbir şey yoktur :

IFS=$'\n'; while read -r x; do ...; done </tmp/file    # Also success

ve görebildiğim bu yapı select:

IFS=$'\n'; select file in $(find -type f -name 'file*'); do echo "$file"; break; done

Defansif kod yazarken ben fıkra ya bir alt kabukta çalıştırın veya edilmesini öneriyoruz IFSve SHELLOPTSkaydedilir ve blok etrafında restore:

OIFS="$IFS" IFS=$'\n'                     # Split on newline only
OSHELLOPTS="$SHELLOPTS"; set -o noglob    # Wildcards must not expand twice

select file in $(find -type f -name 'file*'); do echo $file; break; done

IFS="$OIFS"
[[ "$OSHELLOPTS" !~ noglob ]] && set +o noglob

5
Varsayıldığında IFS=$'\n'güvenlidir asılsız olduğunu. Dosya adları yeni satır değişmezlerini mükemmel şekilde içerebilir.
Charles Duffy

4
Açıkçası, mevcut olsa bile, kişinin olası veri setiyle ilgili olarak söz konusu iddiaları kabul etmekte tereddüt ediyorum. Şu anda bulunduğum en kötü veri kaybı olayı, eski yedeklemelerin temizlenmesinden sorumlu bir bakım komut dosyasının, rastgele çöp dökümü yapan kötü bir işaretçi dereference olan bir C modülü kullanarak bir Python komut dosyası tarafından oluşturulan bir dosyayı kaldırmaya çalıştığı bir durumdu. - boşlukla ayrılmış bir joker karakter dahil olmak üzere - isme.
Charles Duffy

2
Bu dosyaları temizleme işlemi yapan kabuk betiğini oluşturan kişiler, "büyük olasılıkla" eşleşemediğinden alıntı yapmak için uğraşmadı [0-9a-f]{24}. Müşteri faturalandırmasını desteklemek için kullanılan verilerin yedeklemelerinin TB'si kaybedildi.
Charles Duffy

4
@CharlesDuffy ile tamamen aynı fikirde. Uç vakaları ele almamak yalnızca etkileşimli olarak çalışırken iyidir ve ne yaptığınızı görebilirsiniz . selecttasarımıyla senaryolu çözümler içindir, bu nedenle her zaman kenar kasaları ele alacak şekilde tasarlanmalıdır.
Wildcard

2
@ilkkachu, elbette - selectçalıştırmak için komutları yazdığınız bir kabuktan asla çağırmazsınız , ancak yalnızca bir komut dosyasında, o komut dosyası tarafından sağlanan bir istemi yanıtladığınız ve bu komut dosyasının nerede olduğu bu girdiye dayanarak önceden tanımlanmış mantığın (üzerinde çalışılan dosya adları hakkında bilgi sahibi olmadan oluşturulmuş) yürütülmesi.
Charles Duffy

4

Burada yetki alanım dışında olabilirim ama belki böyle bir şeyle başlayabilirsin, en azından boşlukla ilgili herhangi bir problemi yok:

find -maxdepth 1 -type f -printf '%f\000' | {
    while read -d $'\000'; do
            echo "$REPLY"
            echo
    done
}

Yorumlarda belirtildiği gibi olası yanlış varsayımlardan kaçınmak için yukarıdaki kodun aşağıdakilere eşdeğer olduğunu unutmayın:

   find -maxdepth 1 -type f -printf '%f\0' | {
        while read -d ''; do
                echo "$REPLY"
                echo
        done
    }

read -dakıllı bir çözümdür; Bunun için teşekkürler.
DopeGhoti

2
read -d $'\000'olduğu tam olarak özdeş read -d '', ama (o dizeleri içinde edebi NULs temsil edebilir olduğunu, yanlış, ima) bash yetenekleri hakkında millet yanıltıcı için. Çalıştır'ı s1=$'foo\000bar'; s2='foo'tıklatın ve iki değeri birbirinden ayırmanın bir yolunu bulmaya çalışın. (Gelecekteki bir sürüm, saklanan değeri eşdeğer hale getirerek komut değiştirme davranışı ile normalleşebilir foobar, ancak bugün durum böyle değildir).
Charles Duffy
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.