Gelecekte değiştiğinde bash scriptlerini zarar vermeye karşı nasıl sertleştirebilirim?


46

Böylece, giriş klasörümü sildim (veya daha doğrusu, yazma erişimim olan tüm dosyaları). Olan benim olduğum şeydi

build="build"
...
rm -rf "${build}/"*
...
<do other things with $build>

bir bash betiğinde ve artık gerekmediğinde $build, bildirgeyi ve tüm kullanımlarını kaldırma - ama rm. Bash mutlu bir şekilde genişler rm -rf /*. Evet.

Kendimi aptal hissettim, yedeği kurdum, kaybettiğim işi yeniden yaptım. Utancı geçmeye çalışıyorum.

Şimdi merak ediyorum: bu tür hataların oluşmaması için bash senaryoları yazma teknikleri nelerdir, veya en azından daha az muhtemeldir? Mesela ben yazmıştım

FileUtils.rm_rf("#{build}/*")

Ruby senaryosunda tercüman buildilan edilmemekten şikayetçi olur , bu yüzden orada dil beni korur.

Bash'te düşündüğüm şey, karabata eklemenin yanı sıra rm(ilgili sorulardaki birçok cevabın da belirttiği gibi, problemsiz değildir):

  1. rm -rf "./${build}/"*
    Bu şu andaki çalışmamı (Git repo) başka bir şey değil öldürürdü.
  2. Bunun bir varyantı / parametresi rmmevcut dizinin dışında hareket ederken etkileşim gerektirir. (Hiçbiri bulunamadı.) Benzer etki.

Bu mu, yoksa bu anlamda "sağlam" olan bash scriptleri yazmanın başka yolları var mı?


3
İçin hiçbir neden yoktur rm -rf "${build}/*alıntı işaretleri nereye olursa olsun. rm -rf "${build} nedeniyle aynı şeyi yapacağım f.
Monty Harder,

1
Shellcheck.net ile statik kontrol, başlamak için çok sağlam bir yer. Mevcut editör entegrasyonu vardır, bu yüzden hala kullanılan bir şeyin tanımını kaldırır kaldırmaz araçlarınızdan bir uyarı alabilirsiniz.
Charles Duffy

Benim utanç @CharlesDuffy için eklenti, ben BashSupport yüklü bir IDEA tarzı IDE, bu senaryoyu yazdım gelmez bu durumda uyarır. Yani evet, geçerli nokta, ama gerçekten zor bir ipucuna ihtiyacım vardı.
Raphael,

2
Anladım. BashFAQ # 112'deki uyarıları not ettiğinizden emin olun . set -uNeredeyse eskisi gibi kaşlarını çatmak değil set -e, ama yine de var.
Charles Duffy

2
şaka cevabı: #! /usr/bin/env rubyHer kabuk betiğinin en üstüne koyun ve bash hakkında unutun;)
Pod

Yanıtlar:


75
set -u

veya

set -o nounset

Bu, mevcut kabuğun, tanımlanmamış değişkenlerin genişlemelerini hata olarak işlemesini sağlar:

$ unset build
$ set -u
$ rm -rf "$build"/*
bash: build: unbound variable

set -uve set -o nounsetolan POSIX kabuk seçenekleri .

Bir boş değer olurdu değil olsa bir hata tetikler.

Bunun için kullan

$ rm -rf "${build:?Error, variable is empty or unset}"/*
bash: build: Error, variable is empty or unset

Genişlemesi, boş veya ayarsız olmadığı sürece ${variable:?word}genişler variable. Boş veya ayarlanmamışsa, wordstandart hatada görüntülenir ve kabuk genişlemeyi bir hata olarak görür (komut yürütülmez ve etkileşimli olmayan bir kabukta çalışıyorsa bu sonlandırılır). Çıkma, :hatayı yalnızca olduğu gibi, ayarlanmamış bir değer için tetikler set -u.

${variable:?word}a, POSIX parametre genişletme .

Bunların hiçbiri set -e(veya set -o errexit) de geçerli olmadıkça etkileşimli bir kabuğun sonlanmasına neden olmaz. ${variable:?word}değişken boş veya ayarlanmamışsa komut dosyalarının çıkmasına neden olur. set -uile birlikte kullanıldığında bir komut dosyasının çıkmasına neden olur set -e.


İkinci sorunuza gelince. rmGeçerli dizinin dışında çalışmama sınırlaması yoktur .

GNU uygulamasının onu tekrarlanan bir şekilde monte edilmiş dosya sistemlerini silmesini engelleyen rmbir --one-file-systemseçeneği vardır, ancak rmaramayı gerçekte argümanları kontrol eden bir fonksiyona sarmadan alabileceğimize inandığımız kadar yakın bir seçenek .


Bir yan not olarak: genişleme, hemen izleyen karakterin, gibi bir değişken adında geçerli bir karakter olduğu bir dizgenin parçası olarak oluşmadığı sürece ${build}tamamen eşdeğerdir .$build"${build}x"


Çok iyi teşekkürler! 1) Öyle mi ${build:?msg}yoksa ${build?msg}? 2) Derleme komut dosyaları gibi bir şey bağlamında, güvenli silme için rm'den farklı bir araç kullanmanın iyi olacağını düşünüyorum: mevcut dizinin dışında çalışmak istemediğimizi biliyoruz , bu yüzden kısıtlı kullanarak açık hale getiriyoruz komut. Genel olarak rm'yi daha güvenli hale getirmeye gerek yok.
Raphael,

1
@Raphael Üzgünüz, olması gerekenler:. Şimdi bu yazım hatasını düzelteceğim ve bilgisayarıma döndüğümde önemini daha sonra anlatacağım.
Kusalananda

2
@Raphael Tamam, bunlar olmadan ne olacağı hakkında kısa bir cümle ekledim :(hatayı sadece belirsiz değişkenler için tetikler ). Her koşulda yalnızca belirli bir yol altındaki dosyaları silmeyle başa çıkacak bir araç yazmaya çalışmıyorum. Yolları ayırmak ve sembolik bağları vb. Umursamamak / umursamamak, şu anda benim için biraz zor.
Kusalananda

1
Teşekkürler, açıklama yardımcı olur! "Yerel rm" ile ilgili olarak: çoğunlukla musing, belki de "emin, bu <toolname>" için balık tutuyordum - ama kesinlikle yazmak için yardım etmeye çalışmıyorum! OO Her şey yolunda, yeterince yardım ettiniz! :)
Raphael,

2
@ MateuszKonieczny Yapacağımı sanmıyorum, üzgünüm. Gerektiğinde bunun gibi güvenlik önlemlerinin kullanılması gerektiğine inanıyorum . Bir ortamı güvenli kılan her şeyde olduğu gibi, sonunda insanları daha dikkatsiz ve güvenlik önlemlerine bağlı hale getirecek. Her güvenlik önleminin ne yaptığını bilmek ve daha sonra bunları gerektiğinde seçici bir şekilde uygulamak daha iyidir.
Kusalananda

12

test/ Kullanarak normal doğrulama kontrolleri önereceğim[ ]

Senaryonuzu şöyle yazmış olsaydınız güvende olsaydınız:

build="build"
...
[ -n "${build}" ] || exit 1
rm -rf "${build}/"*
...

[ -n "${build}" ]Çekler "${build}"bir olan sıfır olmayan uzunlukta dize .

||Bash mantıksal VEYA operatörü olduğunu. İlki başarısız olursa, başka bir komutun çalıştırılmasına neden olur.

Bu sayede ${build}boş / tanımsız / etc olmuştu . komut dosyası çıkacaktı (genel bir hata olan 1 dönüş koduyla).

Bu, aynı zamanda, tüm kullanımları kaldırmanız durumunda sizi koruyacaktı ${build}çünkü [ -n "" ]daima yanlış olacaktır.


Kullanmanın avantajı test/ [ ]kullanabileceği daha birçok anlamlı kontrol var.

Örneğin:

[ -f FILE ] True if FILE exists and is a regular file.
[ -d FILE ] True if FILE exists and is a directory.
[ -O FILE ] True if FILE exists and is owned by the effective user ID.

Adil, ama biraz hantal. Bir şey mi eksik veya ${variable:?word}@ Kusalananda'nın önerdiği gibi hemen hemen aynı mı?
Raphael,

@Raphael, "dizgenin bir değeri var mı" durumunda benzer şekilde çalışır ancak test(yani [) -d(argüman bir dizindir), -f(argüman bir dosyadır), -O( dosya argüman) Geçerli kullanıcı tarafından sahip olunan) ve benzeri.
Centimane

1
@Raphael ayrıca, doğrulama herhangi bir kod / komut dosyasının normal bir parçası olmalıdır. Eğer yapı tüm örneklerini kaldırılır Ayrıca not, sen de kaldırılır olmazdı ${build:?word}onunla birlikte? ${variable:?word}Eğer değişkenin tüm örneklerini kaldırırsanız biçimi sizi korumaz.
Centimane

1
1) Varsayımların tutulmasını sağlamak (dosyalar var, vb.) İle değişkenlerin ayarlanıp ayarlanmadığını (bir derleyici / tercüman işinden) arasında bir fark olduğunu düşünüyorum. İkincisini el ile yapmak zorunda kalırsam, bunun için ultra garip bir sözdizimi olsa iyi olur - ki tam anlamıyla şişirilmiş ifdeğildir. 2) "$ {build:? Word} 'in onunla birlikte kaldırmasını da istemezdiniz" - senaryo, değişkenin kullanımını kaçırmış olmamdır. Kullanmak ${v:?w}beni hasardan koruyacaktı. Tüm kullanımları kaldırmış olsaydım, düz erişim bile açıkça belli olmazdı.
Raphael

Tüm bunların söylediği gibi, cevabınız genel titüler soruya adil ve kesin bir cevaptır: varsayımların geçerli olmasını sağlamak , etrafta kalan senaryolar için önemlidir. Soru gövdesindeki özel konu, Kusalananda tarafından daha iyi cevaplandırılmış bir imho. Teşekkürler!
Raphael

4

Özel bir durumda, dosyaları / dizinleri taşımak için geçmişte 'silme' işlemini elden geçirdim (/ tmp'nin dizininizle aynı bölümde olduğunu varsayarak):

# mktemp -d is also a good, reliable choice
trashdir=/tmp/.trash-$USER/trash-`date`
mkdir -p "$trashdir"
...
mv "${build}"/* "$trashdir"
...

Sahnelerin ardında, üst düzey dosya / dir referanslarını kaynaktan $trashdiraynı hedef bölümdeki kaynaklara hedef dizin yapılarına taşır ve dizin yapısını yürürken ve oradaki dosya blok disklerini serbest bırakmak için zaman harcamaz. Bu, sistem aktif kullanımdayken, biraz daha yavaş bir yeniden başlatma yerine (/ tmp yeniden başlatıldığında temizlenir) çok daha hızlı temizleme sağlar.

Alternatif olarak, /tmp/.trash-$USER öğesinin periyodik olarak temizlenmesi için bir cron girişi / tmp'nin çok fazla disk alanı tüketen işlemler (örneğin, oluşturmalar) için doldurmalarını engelleyecektir. Dizininiz / tmp olarak farklı bir bölümdeyse, bölümünüzde benzer / tmp benzeri bir dizin oluşturabilir ve bunun yerine cron'u temizleyebilirsiniz.

En önemlisi, değişkenleri herhangi bir şekilde karıştırırsanız, temizleme gerçekleşmeden önce içeriği kurtarabilirsiniz.


2
"Bu sistem aktif kullanımdayken daha hızlı temizleme sağlar" - yapar mı? Her ikisinin de yalnızca etkilenen düğümleri değiştirmekle ilgili olduğunu düşünürdüm. Başka bir bölümde ise kesinlikle daha hızlı değil/tmp (bence her zaman benim için, en azından kullanıcı alanında çalışan betikler için); o zaman, çöp klasörünün değiştirilmesi gerekiyor (ve işletim sistemi işlemesinden kar elde etmiyor /tmp).
Raphael,

1
Bu mktemp -dgeçici dizini başka bir işlemin ayak parmaklarına basmadan (ve doğru şekilde onurlandırmak $TMPDIR) almak için kullanabilirsiniz.
Toby Speight

İkinizden de doğru ve faydalı notlarınızı ekledik, teşekkürler. Sanırım bunu ortaya koyduğunuz noktaları göz önünde bulundurmadan çok hızlı bir şekilde gönderdim.
user117529

2

Değişken başlatılmamışsa varsayılanları atamak için bash paramater ikameini kullanın, örneğin:

rm -rf $ {değişken: - "/ varolmayan"}



1

Değişkeninizin ayarlanıp ayarlanmadığını kontrol etmek için genel tavsiye, bu tür bir sorunu önlemek için yararlı bir araçtır. Ancak bu durumda daha basit bir çözüm vardı.

Büyük olasılıkla, $builddizinin içeriğini silmek için, onları silmek için değil, gerçek $builddizinin kendisini değil. Eğer yabancıyı atlarsanız, *o zaman belirsiz bir değer rm -rf /, son on yıldaki varsayılan olarak çoğu rm uygulamasının gerçekleştirmeyi reddedeceği bir şeye dönüşür (GNU rm'nin --no-preserve-rootseçeneği ile bu korumayı devre dışı bırakmazsanız ).

Sondaki atlama /de neden olacaktır olarak rm ''hata mesajı ile sonuçlanacaktır ki:

rm: can't remove '': No such file or directory

Bu, rm komutunuz için koruma sağlamazsa bile işe yarar /.


-2

Dil terimlerini programlamayı düşünüyorsunuz ama bash bir betik dilidir :-) Yani, tamamen farklı bir dil paradigması için tamamen farklı bir öğretim paradigması kullanın .

Bu durumda:

rmdir ${build}

Yana rmdirboş olmayan bir dizini kaldırmak için reddeder, öncelikle üye kaldırmak gerekir. Bu üyelerin ne olduğunu biliyorsun, değil mi? Muhtemelen betiğinizin parametresidir veya bir parametreden türemiştir, bu nedenle:

rm -rf ${build}/${parameter}
rmdir ${build}

Şimdi, bir geçici dosya veya olması gereken bir şey gibi oraya başka dosya veya dizinler koyarsanız, rmdirbir hata verecektir. Doğru kullan, sonra:

rmdir ${build} || build_dir_not_empty "${build}"

Bu düşünce tarzı bana çok iyi hizmet etti çünkü ... evet, orada bulundun, bunu yaptım.


6
Çabalarınız için teşekkürler, ama bu tamamen işaretsiz. "Bu üyelerin ne olduğunu biliyorsunuz" varsayımı yanlıştır; derleyici çıktısını düşünün. make cleanbazı joker karakterler kullanır (çok çalışkan bir derleyici oluşturduğu her dosyanın bir listesini oluşturmazsa). Ayrıca, bana göre rm -rf ${build}/${parameter}bu konuyu sadece biraz aşağıya kaydırmak gibi görünüyor .
Raphael,

Hm. Nasıl okuduğuna bir bak. “Teşekkür ederim, ama bu asıl soruda açıklamadığım bir şey için , bu nedenle genel cevabınıza daha uygulanabilir olmasına rağmen sadece cevabınızı reddetmeyeceğim, ancak reddedeceğim.” Vay.
Zengin

"Bana soruda yazdıklarınızın ötesinde ek varsayımlarda bulunayım ve sonra özel bir cevap yazalım." * shrug * (Bilginize, bu sizin ilgilendirmez değil: Ben yanıtın yararlı değildi çünkü downvote Muhtemelen ben en azından bana (sahip olmalıdır yoktu)..)
Raphael

Hm. Hiçbir varsayımda bulunmadım ve genel bir cevap yazdım . Soruda hiçbir şey yok make. Sadece uygulanabilir bir cevap yazmak makeçok özel ve şaşırtıcı bir varsayım olurdu. Cevabım make, yazılım geliştirme dışındaki, kendi acemi probleminizden başka, eşyalarınızı silerek yaşadığınız durumlar için işe yarıyor .
Zengin,

1
Kusalananda bana balık tutmayı öğretti. Geldin ve "Ovalarda yaşadığından beri neden et yemezsin?" Dedin.
Raphael,
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.