Cara menjalankan perintah di semua subdirektori menggunakan Bash

2

Saya memiliki struktur direktori yang disusun oleh:

iTunes/Music/${author}/${album}/${song.mp3}

Saya menerapkan skrip untuk memotong bitrate mp3 saya ke 128 kbps menggunakan lumpuh (yang bekerja pada satu file pada suatu waktu).

'Normalize_mp3.sh' saya terlihat seperti ini:

#!/bin/bash
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in *.mp3
do
  lame --cbr $f __out.mp3
  mv __out.mp3 $f
done
IFS=$SAVEIFS

Ini berfungsi dengan baik jika saya membuka folder demi folder dan menjalankan perintah ini, tetapi saya ingin memiliki perintah "global", seperti pada 4DOS, jadi saya dapat menjalankan:

$ cd iTunes/Music
$ global normalize_mp3.sh

dan global perintah akan melintasi semua subdirs dan menjalankan normalize_mp3.sh untuk menghapus semua mp3 saya di semua subfolder.

Adakah yang tahu kalau ada Unix yang setara dengan 4DOS global perintah? Saya mencoba bermain find -exec tapi saya sakit kepala saja.

Luigi R. Viggiano
sumber

Jawaban:

2

Ini adalah implementasi yang sangat mendasar dari yang dijelaskan global perintah untuk bash:

global() {
  shopt -s globstar
  origdir="$PWD"
  for i in **/; do
    cd "$i"
    echo -n "${PWD}: "
    eval "$@"
    echo
    cd "$origdir"
  done
}
  • shopt -s globstar memungkinkan globbing rekursif dengan ** (Saya pikir Anda akan memerlukan versi bash & gt; = 4)
  • **/ menemukan semua direktori di bawah direktori kerja saat ini, jadi $i loop atas mereka
  • Saya termasuk a echo sehingga Anda dapat melihat di mana dir loop saat ini
  • eval "$@" mengevaluasi semua argumen fungsi
  • cd $origdir kembali ke direktori asli. (Awalnya saya punya pendekatan IMHO lebih elegan dengan pushd / popd, tapi saya rasa cd kembali ke $origdir lebih tahan peluru jika perintah "$@" itu sendiri mengubah direktori (yang tentu saja merupakan ide yang buruk!)

Ini global perintah mengambil perintah dengan argumen seperti global touch foo. Jika Anda ingin menggunakan sintaksis shell lebih lanjut seperti pengalihan atau variabel, Anda harus menyertakan perintah dalam tanda kutip tunggal '...'. Tetapi jika keinginan Anda adalah untuk menjalankan skrip Anda normalize_mp3.sh di setiap direktori penggunaannya sesederhana

global normalize_mp3.sh

Sadarilah, bahwa mulai dari perintah pertama dapat membutuhkan waktu untuk mengumpulkan semua subdirs jika ada banyak.

Dan, tentu saja, silakan uji dengan cadangan file Anda yang terbaru!


Inilah cara kerjanya (saya hanya dapat menguji dengan Linux, tapi saya berasumsi bahwa bash bekerja pada OSX dengan cara yang sama):

$ echo $BASH_VERSION 
4.1.5(1)-release
$ cd /var
$ tree -d | head
.
├── backups
├── cache
│   ├── apt
│   │   ├── apt-file
│   │   └── archives
│   │       └── partial
│   ├── cups
│   │   └── rss
│   ├── debconf
$ global 'echo -n This command is executed in $PWD at $(date); sleep 1'
/var/backups: This command is executed in /var/backups at Mo 1. Jul 12:11:00 CEST 2013
/var/cache: This command is executed in /var/cache at Mo 1. Jul 12:11:01 CEST 2013
/var/cache/apt: This command is executed in /var/cache/apt at Mo 1. Jul 12:11:02 CEST 2013
/var/cache/apt/apt-file: This command is executed in /var/cache/apt/apt-file at Mo 1. Jul 12:11:03 CEST 2013
/var/cache/apt/archives: This command is executed in /var/cache/apt/archives at Mo 1. Jul 12:11:04 CEST 2013
/var/cache/apt/archives/partial: This command is executed in /var/cache/apt/archives/partial at Mo 1. Jul 12:11:05 CEST 2013
/var/cache/cups: This command is executed in /var/cache/cups at Mo 1. Jul 12:11:07 CEST 2013
/var/cache/cups/rss: This command is executed in /var/cache/cups/rss at Mo 1. Jul 12:11:08 CEST 2013
/var/cache/debconf: This command is executed in /var/cache/debconf at Mo 1. Jul 12:11:09 CEST 2013
...
mpy
sumber
dalam pemahaman saya ini tidak benar-benar rekursif, tetapi terima kasih telah mencoba, Anda telah menjadi satu-satunya: Anda mendapatkan +50
Luigi R. Viggiano
Meski begitu saya merasa pertanyaannya cukup menarik ...
Luigi R. Viggiano
Apa yang Anda pahami dengan "sangat rekursif"? Perintah ini tidak "melintasi semua subdir" dan mengeksekusi perintah di setiap dir itu. Apakah tidak berfungsi dengan pengaturan Anda? Untuk membuatnya berfungsi, Anda harus menggunakan versi bash terbaru (& gt; = 4.0) - Saya kira bash bekerja di OSX dengan cara yang sama seperti di Linux ?!
mpy
Sementara itu saya punya ide bagaimana menggabungkan variabel dll dalam perintah (saya akan meningkatkan jawaban saya nanti) - itu sangat sederhana, tetapi kadang-kadang Anda hanya perlu mundur dan melihat keseluruhannya.
mpy
Itu luar biasa. Saya bertanya-tanya mengapa sesuatu seperti ini tidak ada di utix utils. Anda harus meningkatkan alat ini dan membuka sumber ini.
Luigi R. Viggiano
5

Pilihan lain:

find ~/Music -name \*.mp3 | while IFS= read -r f; do
  lame --cbr "$f" "$f"_temp
  mv "$f"_temp "$f"
done
mdfind 'kMDItemAudioBitRate>128000' -onlyin ~/Music |\
parallel lame --cbr {} {}_temp \; mv {}_temp {}
f() { lame --cbr "$1" "$1"_temp; mv "$1"_temp "$1"; }
export -f f
mdfind 'kMDItemAudioBitRate>128000' -onlyin ~/Music | parallel f
shopt -s globstar # bash 4.0 or later
for f in ~/Music/**/*.mp3; do lame --cbr "$f" "$f"_temp; mv "$f"_temp "$f"; done
Lri
sumber
+1 Ini jelas merupakan tugas untuk find. Bahkan jika Anda tidak ingin memodifikasi skrip, Anda dapat membatasi find perintah untuk hanya menampilkan direktori dan menjalankan skrip Anda melalui mereka.
Squeezy
1

Coba sesuatu seperti:

#!/bin/bash
# first cd to iTunes/Music
for f in */*/*.mp3
do 
  lame --cbr "$f" __out.mp3 && mv __out.mp3 "$f"
done

Tidak perlu pengaturan IFS. Anda perlu memberikan tanda kutip ganda $f meskipun.. Saya tidak tahu apakah lumpuh memiliki kode pengembalian jika terjadi kesalahan. Mungkin pertama kali mencobanya dengan printf "%s\n" "$f" first bukannya perintah lumpuh ..

Scrutinizer
sumber
1

Pertama-tama, miliki skrip yang sedikit lebih ramping itu loop melalui argumen yang disediakan untuk skrip .

#! /usr/bin/env bash
for f do
  lame --cbr "$f" "$f_out.mp3"
  mv "$f_out.mp3" "$f"
done

Agar dapat diakses dari mana saja, letakkan skrip di suatu tempat di $ PATH Anda. Di Linux, saya akan gunakan $HOME/bin, yang ditambahkan ke $ PATH secara default; Saya menduga ini juga berlaku untuk OSX.

Gunakan di satu direktori mp3 dengan:

downrate_mp3 ./*.mp3

Saya lebih suka awalan dengan gumpalan ini ./, karena sah untuk memulai nama file dengan a -, yang dapat menyebabkan masalah karena banyak program (termasuk mv ) mengharapkan argumen yang dimulai dengan a - menjadi opsi, bukan file. Sebagai pengulangan, cara terbaik adalah menggunakan globstar (membutuhkan bash 4+, tapi saya pikir itulah yang dimiliki OSX baru-baru ini):

shopt -s globstar
downrate_mp3 ./**/*.mp3

Atau, Anda dapat menggunakan find with -exec, seperti yang sudah Anda coba:

find iTunes/Music -type f -name '*.mp3' -exec downrate_mp3 {} +

Alasan ini tidak akan bekerja dengan perintah Anda yang ada adalah itu find pada dasarnya mencoba menggunakan daftar besar file sebagai argumen untuk skrip - dan karena skrip dalam pertanyaan Anda tidak melakukan apa pun dengan argumen, ini jelas tidak akan berhasil.

evilsoup
sumber