Dosya adlarını boşluklarla (veya geçici çözümlerle) işlemek için BASH komut dosyası oluşturma


12

BASH'i birkaç yıldır kullanırken, BASH komut dosyası oluşturma deneyimim nispeten sınırlı.

Kodum aşağıdaki gibidir. Tüm dizin yapısını geçerli dizinden almalı ve içine kopyalamalıdır $OUTDIR.

for DIR in `find . -type d -printf "\"%P\"\040"`
do
  echo mkdir -p \"${OUTPATH}${DIR}\"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done

Sorun, benim dosya yapısı bir örnek:

$ ls
Expect The Impossible-Stellar Kart
Five Iron Frenzy - Cheeses...
Five Score and Seven Years Ago-Relient K
Hello-After Edmund
I Will Go-Starfield
Learning to Breathe-Switchfoot
MMHMM-Relient K

Boşluklara dikkat edin: -S Ve forparametreleri kelime kelime alır, bu yüzden betiğimin çıktısı şöyle görünür:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Learning"
Created Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot"
Created Breathe-Switchfoot

Ama çıkışından tüm dosya adlarını (bir seferde bir satır) almak için buna ihtiyacım var find. Ben de findher dosya adı etrafında çift tırnak koymak denedim . Ama bu yardımcı olmuyor.

for DIR in `find . -type d -printf "\"%P\"\040"`

Ve bu değişen çizgi ile çıktı:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"""
Created ""
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"Learning"
Created "Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot""
Created Breathe-Switchfoot"

Şimdi, bu şekilde yineleyebileceğim bir yola ihtiyacım var, çünkü gstreameraşağıdaki benzer yapıdaki her dosyada daha karmaşık bir komut çalıştırmak istiyorum . Bunu nasıl yapmalıyım?

Düzenleme: Bana her dizin / dosya / döngü için birden çok kod satırı çalıştırmak sağlayacak bir kod yapısı gerekir. Belirsiz olsaydım özür dilerim.

Çözüm: Başlangıçta denedim:

find . -type d | while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done

Bu çoğunlukla iyi çalıştı. Ancak, daha sonra, borunun bir alt kabukta while döngüsü ile sonuçlandığından, döngüde ayarlanan değişkenlerin daha sonra kullanılamadığını ve bu da bir hata sayacının uygulanmasını oldukça zor hale getirdiğini buldum. Son çözümüm (SO'daki bu cevaptan ):

while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done < <(find . -type d)

Bu daha sonra komut dosyasında daha sonra kullanılabilecek döngü içindeki değişkenleri koşullu olarak artırmama izin verdi.


Why_would_you_ever_need_a_space_in_a_file_name?
Kevin Panko

Doğru, benim tercihim değil. Boşlukları kaldırmak için önce boşluklu dosyaları işlemeniz gerekir;)
Samuel Jaeschke

1
Aslında, dosya adları boşluklara izin vermelidir. Başka bir şeye izin vermezdim /ve basılamaz karakterlerden. Ama dışında her şeye izin verilir /ve \0bu yüzden onlara izin vermelisin.
Kevin Panko

Yanıtlar:


11

Sen boruya ihtiyaç findbir içine whiledöngü.

find ... | while read -r dir
do
    something with "$dir"
done

Ayrıca, -printfbu durumda kullanmanız gerekmez .

Bu kanıtı, adlarında yeni satırları olan dosyalara karşı, isterseniz, nullbyte sınırlayıcı (* nix dosyayolunda görünmeyen tek karakter olan) kullanarak yapabilirsiniz:

find ... -print0 | while read -d '' -r dir
do
    something with "$dir"
done

Ayrıca $(), daha çok yönlü ve daha kolay olması için ters çentikler yerine kullanmayı da bulacaksınız . Çok daha kolay iç içe yerleştirilebilirler ve alıntılar çok daha kolay yapılabilir. Bu örnek, şu noktaları gösterecektir:

echo "$(echo "$(echo "hello")")"

Bunu ters çentikler ile yapmaya çalışın.


2
Ayrıca, kullanmak yerine "$dir", tercih edilir "${dir}"- $ {dir} name ve $ {dirname} arasındaki farkı söylemek kolaydır, ancak $ dirname her iki şekilde de yorumlanabilir.
James Polley

Burada önemli olan read, tüm satırı okur ${dir}, bu yüzden IFS önemli değil.
James Polley

1
$ / "Yazım hatasını bulduğunuz için teşekkür ederiz. Değişken adını takip eden bir şey yoksa parantez gerekli değildir.
sonraki duyuruya kadar duraklatıldı.

4
Bu, boşluk içeren yol adlarını (U + 0020) işleyecektir, ancak yine de satır beslemeli yol adlarını (U + 000A) düzgün işlemeyecektir. find … -print0 | xargs -0 …Kullandığım ayırıcı tam olarak POSIX pathanamesinde izin verilmeyen tek karaktere karşılık geldiğinden tercih ederim : NUL (U + 0000).
Chris Johnsen

2
Mükemmel! Tam da aradığım şey. Pipolayabileceğiniz hiç aklıma gelmemişti while. @Chris Johnsen: Doğru, ama müzik sökme programları bile dosya adlarına satır beslemeleri koyma eğiliminde değil. Ve eğer yaparlarsa, bilmek istiyorum (yani: bir şeyler ters gidiyor) ve onlardan hemen kurtulmak istiyorum ...
Samuel Jaeschke

8

Birkaç gün önce dosya adlarını boşluklarla işleyen bir komut dosyası örneği için yazdığım bu cevaba bakın .

Yine de yapmaya çalıştığınız şeyi elde etmenin biraz daha kıvrımlı (ama daha özlü) bir yolu var:

find . -type d -print0 | xargs -0 -I {} mkdir -p ../theredir/{}

-print0argümanları null ile ayırmayı söyler; -0 ile xargs arasında null ile ayrılmış argümanlar beklenir. Bu, boşlukları iyi idare ettiği anlamına gelir.

-I {}xargs'a dizeyi {}dosya adıyla değiştirmesini söyler . Bu aynı zamanda komut satırı başına yalnızca bir dosya adının kullanılması gerektiği anlamına gelir (xargs normalde satıra sığacak kadar doldurur)

Gerisi açık olmalı.


Bununla birlikte, Dennis Williamson'ın önerisi (yazım hatalarının yanı sıra) çok daha okunabilir ve bu nedenle hemen hemen her şekilde tercih edilir.
James Polley

Çalışır, mkdir için, ama üzgünüm daha açık olmalıydı - Her dosya için bir dizi komut çalıştırmak istiyorum. Görüyorsunuz, benzer rutinim için daha sonra girdi dosya adına (.ogg uzantısını sıyırma ve .mp3 eklemeyi içeren) bir çıktı dosya adı oluşturmak ve daha sonra gst-launch çağrılırken bu çoklu değişkenleri pipline'mda kullanmak istiyorum.
Samuel Jaeschke

5

Karşılaştığınız sorun, for ifadesinin bulmaya ayrı argümanlar olarak yanıt vermesidir. Alan sınırlayıcı. Boşluğa bölünmemek için bash'ın IFS değişkenini kullanmanız gerekir.

İşte bunun nasıl yapılacağını açıklayan bir bağlantı .

IFS iç değişkeni

Bu sorunun bir yolu, Bash'in dahili IFS (Dahili Alan Ayırıcı) değişkenini, alanları varsayılan boşluktan (boşluk, sekme, satırsonu) başka bir şeyle (boşluk, sekme, satırsonu) bölerek değiştirmektir.

#!/bin/bash
IFS=$';'

for I in `find -type d -printf \"%P\"\;`
do
   echo "== $I =="
done

Bulgunuzu% P'den sonra alan sınırlayıcınızın çıktısını alacak şekilde ayarlayın ve IFS'nizi uygun şekilde ayarlayın. Dosya adlarınızda bulunma olasılığı düşük olduğundan noktalı virgül seçtim.

Diğer bir alternatif ise mkdir'i doğrudan bulup arayarak -execfor döngüsünü tamamen atlayabilirsiniz. Ek ayrıştırma yapmanız gerekmiyorsa.


Dosya adı IFS içeriyorsa ne olur? O zaman farklı bir tane seçmelisin. Ama sonra, ya ...
sonraki duyuruya kadar duraklatıldı.

3
/POSIX'i ve :DOS dosya sistemlerini seçebilirsiniz . IFS için seçebileceğiniz farklı dosya sistemleri için geçersiz karakterler var. Daha karmaşık bir şey ve perl kullanarak daha iyi.
Darren Hall

2
/ Kullanmayla ilgili sorun, dizin sınırlayıcısı olması ve findeğik çizgi içeren yollarla dosya adları döndürmesidir. Komut dosyanızdaki noktalı virgül eğik çizgiyle değiştirmeyi deneyin; yankı dizini ve dosya adını ayrı satırlara yazdırır.
sonraki duyuruya kadar duraklatıldı.

Bu da oldukça faydalı görünüyor. Ben boru ile whileseçenek gitti , ama bu da oldukça uygulanabilir görünüyor. Evet, benzer yapımda daha sonra ayrıştırma yapmak zorunda kaldım. (Girdi dosya adı filesrc, gst kanalında olduğu gibi geçirilecek olan .ogg olurdu , ancak çıktı dizininde bulunan .mp3 ile biten eşdeğer bir son üretilecek ve aynı zamanda boru hattına da aktarılacak filesinkve bu da elbette yapılmalıdır. her dosya için bazı echokullanıcılarla birlikte.)
Samuel Jaeschke

4

Döngünüzün gövdesi tek bir komuttan daha fazlaysa, kabuk komut dosyası çalıştırmak için xargs kullanmak mümkündür :

export OUTPATH=/some/where/else/
find . -type d -print0 | xargs -0 bash -c 'for DIR in "$@"; do
  printf "mkdir -p %q\\n" "${OUTPATH}${DIR}"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done' -

Kabuk Bourne / POSIX çeşidindeyse (kabuk betiğinde 0 $ ayarlamak için kullanılır) arka çizgi (veya başka bir 'kelime') eklediğinizden emin olun. Ayrıca, kabuk komut dosyası doğrudan bilgi istemi yerine tırnak içine alınmış bir dizenin içine yazıldığından, tırnak işareti ile dikkatli olunmalıdır.


Başka bir ilginç kavram. Teşekkürler - Eminim bunun için bir kullanım bulacağım :)
Samuel Jaeschke

1

güncellenmiş sorunuzda var

mkdir -p \"${OUTPATH}${DIR}\"

bu olmalı

mkdir -p "${OUTPATH}${DIR}"

Teşekkürler. Sabit. Ayrıca DIR yerine FILENAME adlı
videoya

1
find . -type d -exec mkdir -p "{}\040" ';' -exec echo "Created {}\040" ';'

0

ya da her şeyi daha az karmaşık hale getirmek için:

% rsync -av --include='*/' --exclude='*' SRC DST

bu, SRC'nin dizin yapısını DST'ye çoğaltır.


Hayır, her dosya için birden fazla kod satırı çalıştırmamı sağlayan böyle bir yinelemeli yapıya ihtiyacım var. "Şimdi, bu şekilde yineleyebileceğim bir yönteme ihtiyacım var, çünkü aynı dosyada her dosyada gstreamer ile ilgili daha karmaşık bir komut çalıştırmak istiyorum." Belirsiz olsaydım özür dilerim.
Samuel Jaeschke

verdiğim komut istediğin sorunu çözüyor, bu senin tarafında daha büyük bir 'boru hattının' bir parçası olup olmadığı önemli değil. Sorunda açıklandığı gibi sorunu olan bir başkası için rsync-yaklaşımı işe yarayacaktır. Bu yüzden, potansiyel belirsizlik hakkında üzülmenize gerek yok :)
akira

Evet. Hayır, benzer kullanarak olurdu demek while... do... done, eko, gst-lansman vb (dizesini değiştirmek her dosya üzerinde çalıştırılacak kod birkaç satır gerektirir find benzer işleme yapmak için daha sonra yapıyı ) ve rsyncbunu başaramaz. Bu yüzden benzer bir yapı içinde daha karmaşık bir komutlar dizisi kullanabilmem gerektiğini belirttim. Senaryom bu döngü yapısını iki kez kullanır, bu yüzden soru için daha az kabuklu olanı yayınladım.
Samuel Jaeschke

0

GNU Parallel http: // www.gnu.org/software/parallel/ yüklüyse bunu yapabilirsiniz:

find . -type d | parallel echo making {} ";" mkdir -p /tmp/outdir/{} ";" echo made {}

Daha fazla bilgi edinmek için GNU Parallel için tanıtım videosunu izleyin: http://www.youtube.com/watch?v=OpaiGYxkSuQ

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.