Manipuluj nazwą pliku potokiem z polecenia find

10

Jestem stosunkowo nowy w Bash i próbuję zrobić coś, co na pozór wydawało się dość proste - uruchom wyszukiwanie w hierarchii katalogów, aby uzyskać wszystkie pliki * .wma, potokuj dane wyjściowe do polecenia, w którym przekonwertuję je na mp3 i zapisz przekonwertowany plik jako .mp3. Myślałem, że polecenie powinno wyglądać następująco (zrezygnowałem z polecenia konwersji audio i zamiast tego używam echa do ilustracji):

$ find ./ -name '*.wma' -type f -print0 | xargs -0 -I f echo ${f%.*}.mp3

Jak rozumiem, argument -print0 pozwoli mi obsługiwać nazwy plików ze spacjami (co wiele z nich robi, ponieważ są plikami muzycznymi). Spodziewam się (w wyniku xargs), że każda ścieżka pliku znalezienia jest przechwytywana wf, i że używając podłańcucha dopasuj / usuń z końca łańcucha, powinienem powtórzyć oryginalną ścieżkę pliku z mp3 rozszerzenie zamiast wma. Jednak zamiast tego wyniku widzę:

*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
...

Tak więc moje pytanie (oprócz konkretnego „co tutaj robię źle”) brzmi: czy wartości, które są wynikiem operacji potoku, muszą być traktowane inaczej w operacjach manipulacji ciągiem niż te, które są wynikiem przypisania zmiennej ?

Howard Dierking
źródło
1
Nie ma powodu, aby skorzystać xargsz find. Pochodzi z -execopcją. Czy możesz po prostu dodać do pytania polecenie, którego zamierzasz użyć, a ktoś może pokazać prawidłowe findpolecenie?
ixtmixilix
tak (jak zauważyłem poniżej), o ile mogę nadal manipulować ciągiem znaków dla każdego wyniku polecenia find (np. {}członka)
Howard Dierking
w rzeczywistości istnieją przypadki skrajne, w których xargsjest bardziej odpowiednie niż exec. Zobacz przykład stosu stackoverflow.com/questions/896808/find-exec-cmd-vs-xargs dla konkretnego przypadku.
d34dh0r53
@ d34dh0r53 Jaki przypadek krawędzi? Wątek, do którego linkujesz, nie wskazuje żadnego.
Gilles „SO- przestań być zły”

Odpowiedzi:

8

Jak już zidentyfikowano inne odpowiedzi, ${f%.*}jest on rozszerzany przez powłokę przed uruchomieniem xargspolecenia. Potrzebujesz tego rozszerzenia, aby miało miejsce raz dla każdej nazwy pliku, ze zmienną powłoki fustawioną na nazwę pliku (przekazanie -I ftego nie robi: xargsnie ma pojęcia zmiennej powłoki, szuka ciągu fw poleceniu, więc jeśli używane np xargs -I e echo …. wykonywałby polecenia takie jak ./somedir/somefile.wmacho .mp3).

Kontynuując to podejście, powiedz, xargsaby wywołać powłokę, która może wykonać rozszerzenie. Lepiej, powiedz find- xargsjest w dużej mierze przestarzałym narzędziem i jest trudne w użyciu, ponieważ nowoczesne wersje find mają konstrukcję, która wykonuje tę samą pracę (i więcej) z mniejszymi trudnościami hydraulicznymi. Zamiast find … -print0 | xargs -0 command …biegać find … -exec command … {} +.

find . -name '*.wma' -type f -exec sh -c 'for f; do echo "${f%.*}.mp3"; done' _ {} +

Argument _znajduje się $0w powłoce; nazwy plików są przekazywane jako argumenty pozycyjne, które for f; do …zapętlają się. Prostsza wersja tego polecenia wykonuje osobną powłokę dla każdego pliku, co jest równoważne, ale nieco wolniejsze:

find . -name '*.wma' -type f -exec sh -c 'echo "${0%.*}.mp3"' {} \;

Tak naprawdę nie musisz go findtutaj używać , zakładając, że używasz stosunkowo nowej powłoki (ksh93, bash ≥4,0 lub zsh). W bash, umieścić shopt -s globstarw twojej .bashrcaby aktywować **/wzorzec glob rekursja w podkatalogów (w ksh, to jest set -o globstar). Potem możesz biec

for f in **/*.wma; do
  echo "${f%.*}.mp3"
done

(Jeśli masz wywołane katalogi *.wma, dodaj [ -f "$f" ] || continuena początku pętli).

Gilles „SO- przestań być zły”
źródło
świetne wyjaśnienia, a także wskazały mi kierunek, którego nawet nie zdawałem sobie sprawy (uaktualnienie mojej powłoki bash, aby uzyskać funkcjonalność globstar) - w końcu rekursywne globowanie było rozwiązaniem, które utrzymywało proste polecenie i osiągnęło wszystko, co próbowałem zrobić . Dzięki!
Howard Dierking
6

W przypadku oceny rozwiązania $ {f%. *}. Mp3 odbywa się w powłoce, w której wywołujesz całe polecenie, a nie w powłoce rozwidlonej przez xargs . A w twojej powłoce nie ma zmiennej f , jest ona zastąpiona pustym łańcuchem.

Rozwiązanie wykorzystujące xargs z -I f :

% cat 1.sh 
#!/bin/sh
f=$1
echo ${f%.*}.mp3
% /bin/ls -1
1.sh
aaa.wma
bbbb.wma
ccccc.wma
% find ./ -name '*.wma' -type f -print0 | xargs -0 -I f ./1.sh f
./aaa.mp3
./ccccc.mp3
./bbbb.mp3

Ale zrobiłbym to w ten sposób:

% find ./ -name '*.wma' -type f | sed 's,\.wma$,.mp3,'
0xFF
źródło