Byta filnman på filer. "BATCH"-style

Permalänk
Medlem

Byta filnman på filer. "BATCH"-style

Hej! Det är så att jag har en _hel hög_ med mp3or. De är sorterade i album, har id3taggar, omslag osv. Det enda problemet är att jag vill bli av med en viss del av filnamnet på _samtliga_filnamn.

Då jag har över 100 mappar så blir detta gangska tjatigt, att gå in i t.ex mapp 12, köra script, cd .., cd 13, köra script osv. Så därför undrar jag om Linux, som är så otroligt anpassbart, kunna göra detta utan att gå in i en enda mapp öht?

Så här gör jag för tillfället:

rocketdog@starbase:~/mp3-temp/12$ cd mp3-temp/ rocketdog@starbase:~/mp3-temp/12$ cd 12 rocketdog@starbase:~/mp3-temp/12$ ls Cover.jpg The Day After The Sabbath 12 - 07. White Witch - Auntie Christy Harlow - 1972.mp3 folder.jpg The Day After The Sabbath 12 - 08. Andrew - Heathens - 1973.mp3 Info.txt The Day After The Sabbath 12 - 09. Brainticket - Watchin' You - 1972.mp3 The Day After The Sabbath 12 - 01. Bad Axe - Blues L.A. - 1976.mp3 The Day After The Sabbath 12 - 10. Attila - Wonder Woman - 1970.mp3 The Day After The Sabbath 12 - 02. Troyka - Natural - 1970.mp3 The Day After The Sabbath 12 - 11. Crushed Butler - Love Fighter - 1969.mp3 The Day After The Sabbath 12 - 03. Bux - If You Want Love - 1976.mp3 The Day After The Sabbath 12 - 12. Ellison - Unchanged World - 1971.mp3 The Day After The Sabbath 12 - 04. Osanna - Lady Power - 1971.mp3 The Day After The Sabbath 12 - 13. Church Of Misery - Master Heartache (Sir Lord Baltimore cover) - 2009.mp3 The Day After The Sabbath 12 - 05. Shinki Chen - It Was Only Yesterday - 1971.mp3 The Day After The Sabbath 12 - 14. Christie - Martian King - 1971.mp3 The Day After The Sabbath 12 - 06. Nazareth - Hard Living - 1974.mp3 rocketdog@starbase:~/mp3-temp/12$ for f in *.mp3; do mv "$f" "${f#The Day After The Sabbath 12 - }"; done rocketdog@starbase:~/mp3-temp/12$ ls 01. Bad Axe - Blues L.A. - 1976.mp3 05. Shinki Chen - It Was Only Yesterday - 1971.mp3 09. Brainticket - Watchin' You - 1972.mp3 13. Church Of Misery - Master Heartache (Sir Lord Baltimore cover) - 2009.mp3 Info.txt 02. Troyka - Natural - 1970.mp3 06. Nazareth - Hard Living - 1974.mp3 10. Attila - Wonder Woman - 1970.mp3 14. Christie - Martian King - 1971.mp3 03. Bux - If You Want Love - 1976.mp3 07. White Witch - Auntie Christy Harlow - 1972.mp3 11. Crushed Butler - Love Fighter - 1969.mp3 Cover.jpg 04. Osanna - Lady Power - 1971.mp3 08. Andrew - Heathens - 1973.mp3 12. Ellison - Unchanged World - 1971.mp3 folder.jpg

Summa sumarum: Hur gör jag alltså för att ändra mp3-filerna med detta kraftfulla verktyg? Vill ju kunna backa till /mp3-temp och sen köra något script som funkar rekursivt, t.ex detta: for f in *.mp3; do mv "$f" "${f#The Day After The Sabbath 12 - }"; done - problemet är ju bara att det funkar lokalt. Tyvärr är jag är grön för att scripta ihop något som funkar för att ta bort en viss text ur ett filnamn, särskilt när filerna ligger rekursivt i mappar.

Visa signatur

Sorcere of Pan Tang

Permalänk
Hedersmedlem

Grundtes: kör inga av de kommandon du får föreslagna på "skarp" data utan att ha backup.

Du vill om jag tolkar det rätt ta bort portionen av filnamnet som börjar med en sträng till och med första förekomsten av - med mellanslag runt bindestrecket (om denna sträng förekommer, men du verkar anta att den alltid gör det). Ett ruskigt snabbt sätt att göra detta (vilket kopplar till ett inlägg jag skrev för bara någon dag sedan) är att kombinera Bashs globstar-alternativ med det Perlbaserade rename-verktyget och köra något i stil med:

shopt -s globstar rename -n 's#/[^/]+? - ([^/]+)$#/$1#' ~/mp3-temp/**/*\ -\ *.mp3

Ovanstående gick snabbt att skriva, men kanske även kräver en förklaring, som tar längre tid :

  • globstar gör att vi kan använda **-konstruktionen för att matcha en okänd mängd underkataloger (se man bash).

  • I Debianbaserade system kommer rename med paketet rename, som installerar /usr/bin/file-rename som även länkas som /usr/bin/rename. Det är ett rekommenderat paket för paketet perl som säkerligen finns installerat, så vanligen finns rename på plats utan att man behövt göra något. Se mitt tidigare länkade inlägg för lite mer information.

  • -n i ovanstående exempel gör att det inte genomförs några ändringar, utan det bara skrivs ut vilka ändringar som skulle göras. Ta bort -n om det verkar OK efter någon testkörning, men se efter noggrant.

  • Den kryptiska texten är ett substitutionsuttryck i Perls syntax för reguljära uttryck.

    • s/foo/bar/ betyder att vi vill byta ut en förekomst av foo mot bar. / kan bytas ut till någon annan avgränsare, vilket jag använder här för att inte "krocka" med / som jag vill matcha i mönstret. Jag använder i stället # som avgränsare.

    • / betyder därmed här att jag vill matcha en katalogavskiljare.

    • [^/]+? - betyder att jag därefter vill matcha "inte en katalogavskiljare" ([^/]) "en eller fler gånger" (+) följt av - , där frågetecknet som läggs till matchningsoperatorn +? gör matchningen "non-greedy" för att stanna vid första förekomsten av - .

    • ([^/]+) betyder att jag återigen vill matcha "inte en katalogavskiljare" "en eller fler gånger" (nu standardalternativet "greedy" (det spelar visserligen ingen roll nu då jag senare förankrar med radslut)), men att jag dessutom vill fånga det som matchar och lagra det i en variabel som jag kan återanvända i mitt substitutionsuttryck.

    • $ betyder i matchningsdelen av uttrycket slutet av strängen.

    • /$1 i substitutionsmönstret betyder att jag vill ersätta hela mönstret som träffade med en katalogavskiljare följt av det som matchades inom parenteserna ("den första" i och med ettan, men här har jag bara en fångad grupp).

    Att jag vill definiera slutet av strängen och hela tiden exkludera katalogavskiljare efter att ha förankrat den initiala är för att vi bara vill döpa om fildelen av namnet, och inte råka träffa en katalog "på vägen" som innehåller - .

  • ~/mp3-temp/**/*\ -\ *.mp3 ger (med globstar aktiverat) alla filer eller kataloger i ~/mp3-temp eller någon av dess underkataloger som innehåller - och har ändelsen .mp3. Notera att det alltså skulle kunna matcha en katalog om du har någon sådan som träffar detta mönster av någon anledning — det skulle i sådana fall vara ett problem, men jag antar att det inte är fallet här.

Dold text

Detta bör gå snabbt då den initiala filnamnslistan skapas direkt i Bash som redan körs, varpå den skickas till en enda subprocess som gör alla substitutioner. Ifall du lyckas matcha fler filer (eller ja, rent tekniskt −1 om vi räknar med mönstret) än vad getconf ARG_MAX har för värde (2 097 152 på mitt system…) så kommer Bash klaga, varpå man i stället skulle kunna lösa filletningen med find. Jag ville använda globstar för att visa att man ofta kan klara sig utan sådant, dock.


En motsvarande lösning med (GNU) find skulle kunna vara:

find ~/mp3-temp -type f -name '* - *.mp3' -exec rename -n 's#/[^/]+? - ([^/]+)$#/$1#' {} +

(see man find, sök efter -exec command) med fördelen att vi inte riskerar att träffa kataloger eller slå i taket vad gäller ARG_MAX (-exec command + håller koll på detta automatiskt och skapar så många extra processer som behövs ifall gränsen skulle överskridas).

Vill man av någon anledning inte använda +-operatorn till -exec utan i stället böka med xargs (det kan finnas anledningar i mer komplexa fall, men inte här) så skulle det kunna se ut enligt:

find ~/mp3-temp -type f -name '* - *.mp3' -print0 | xargs -0 rename -n 's#/[^/]+? - ([^/]+)$#/$1#'

som också löser ARG_MAX-problem på samma sätt.

Att använda -exec command \; skulle däremot gå bra mycket långsammare, då det skulle skapa en process per matchad fil. Sitter man på ett system som inte har GNU-versionen av xargs som stöder -0 för att använda null som argumentavskiljare så kanske -exec command \; trots allt är enklast att använda för att eliminera problem med märkliga filnamn.

Vad gäller det sistnämnda så är det otroligt vanligt att se kommandoförslag på nätet som inte tar hänsyn till udda filnamn — ofta får man vara glad om de ens hanterar filnamn med mellanslag, trots att de hävdar sig vara generella. Ovanstående förslag är alla tänkta att hantera udda filnamn efter bästa förmåga.


Vill du lösa det utan rename så går det att göra i ren Bash mer likt det försök du började med, fast automatiserat och mer robust, genom att exempelvis återigen utnyttja globstar och med fördel även nullglob (se man bash):

shopt -s globstar nullglob for i in ~/mp3_temp/**/; do cd -- "$i" for j in *\ -\ *.mp3; do echo mv -- "$j" "${j#* - }" done # cd - >/dev/null # Se notis done

där jag lagt till echo för att återigen bara skriva ut vad som skulle ske, snarare än att "göra det" för att kunna testa att det faktiskt fungerar som man vill innan man tar bort echo och kör på (notera att echo "äter" citationstecknen i utskriften så som jag skrivit ovan, men att det kommer "bli rätt" i skarpt läge).

Ytterligare försök till förklaring:

  • globstar har vi presenterat tidigare. nullglob (se återigen man bash) gör att ett mönster som inte matchar tas bort, snarare än står kvar som en "literal" (man kan diskutera om inte detta borde varit default-läget, men men).

  • ~/mp3-temp/**/ — den avslutande katalogavskiljaren gör att vi bara matchar just underkataloger här.

  • cd -- "$i" — man bör alltid sätta citationstecken runt variabler som kan innehålla mellanslag om man vill använda dem som enstaka argument. -- är ytterligare en vanlig defensiv konstruktion som säger till verktyg att man nu inte tar emot fler växlar utan bara positionella argument. Utan detta skulle en katalog med namn som en växel (typiskt de som börjar med -) göra oväntade saker.

  • När vi gått till en katalog så loopar vi över alla .mp3-filer i densamma som innehåller strängen vi vill byta ut. nullglob gör att vi bara går vidare ifall mönstret träffar, vilket är bra. Utan detta så hade Bash gått in i loopen med $j satt till strängen * - *.mp3, vilket bara ger felmeddelanden.

  • ${j#* - } — detta är en skalsubstitution som kommer ta bort alla tecken inklusive den första matchningen av - i variabeln $j. # gör det "non-greedy", ## gör det "greedy", % och %% gör motsvarande saker men tittar på strängen från andra hållet.

  • cd - gör att vi går tillbaka till ursprungskatalogen igen inför vårt nästa katalogbyte, för att de potentiellt relativa katalogvägar som matchats ska stämma. I detta fall så matchar jag mot den absoluta vägen ~/mp3-temp/**/ vilket gör att vi loopar över absoluta katalogsökvägar så att denna konstruktion inte behövs (och därmed är bortkommenterad), men jag visar tekniken ändå ifall någon dyker på detta exempel i framtiden, modifierar det och undrar varför allt kollapsar. >/dev/null används för att tysta ned cd - som annars glatt säger vart den går, vilket blir tråkigt när det handlar om hundratals kataloger.

Dold text

Jag har inte kört ovanstående kommandon själv förutom på några enkla testfiler under tiden jag skrev, så som sagt: testa saker innan de körs skarpt. Det är många små detaljer att tänka på ("non-greedy", konstiga filnamn, kataloger vs filer, GNU vs POSIX, nullglob, …), och det vore smått sensationellt om jag inte missat någon ovan. Kanske jag också missat någon möjlig förenkling i matchningen.

Det blev ett längre inlägg än om jag bara skrivit shopt -s globstar; rename -n 's#/[^/]+? - ([^/]+)$#/$1#' ~/mp3-temp/**/*\ -\ *.mp3, men förhoppningsvis vettigare .

Nu med SweC 6.0-formatering!
Visa signatur

Nu med kortare användarnamn, men fortfarande bedövande långa inlägg.

Permalänk
Avstängd
Skrivet av phz:

Vad är "shopt" för något?

Permalänk
Hedersmedlem
Skrivet av rektor:

Vad är "shopt" för något?

"Shell option" — inbyggt kommando i Bash som används för att ändra skalalternativ. Se `man bash` (under sektionen `SHELL BUILTIN COMMANDS`).

`shopt` utan växlar skriver ut nuvarande alternativstatus. `shopt -s alternativ` aktiverar alternativ, `shopt -u alternativ` avaktiverar.

`nullglob`, `failglob`, `extglob`, `dotglob` och `globstar` är särskilt nyttiga alternativ för att kunna göra mer avancerad globbning (typiskt i skript snarare än interaktivt). Det är ofta fördelaktigt att kunna globba rätt filer "direkt", dels då det är effektivt, men framför allt då det hjälper till med att undvika problem med konstiga tecken i filnamn.

Visa signatur

Nu med kortare användarnamn, men fortfarande bedövande långa inlägg.