Ich habe die Idee, ein Bash-Skript auszuführen, um einige Bedingungen zu überprüfen und ffmpeg
alle Videos in meinem Verzeichnis von einem beliebigen Format in ein anderes zu konvertieren, .mkv
und es funktioniert hervorragend!
Die Sache ist, ich wusste nicht, dass eine for file in
Schleife nicht rekursiv funktioniert ( /programming/4638874/how-to-loop-through-a-directory-recursively )
Aber ich verstehe "Piping" kaum und freue mich darauf, ein Beispiel zu sehen und einige Unsicherheiten zu beseitigen.
Ich denke an dieses Szenario, von dem ich denke, dass es mir sehr helfen würde, es zu verstehen.
Angenommen, ich habe dieses Bash-Skript-Snippet:
for file in *.mkv *avi *mp4 *flv *ogg *mov; do
target="${file%.*}.mkv"
ffmpeg -i "$file" "$target" && rm -rf "$file"
done
Suchen Sie für das aktuelle Verzeichnis nach einem beliebigen Verzeichnis und *.mkv *avi *mp4 *flv *ogg *mov
deklarieren Sie die Ausgabe als Erweiterung. .mkv
Anschließend löschen Sie die Originaldatei. Die Ausgabe sollte dann in demselben Ordner gespeichert werden, in dem sich das Originalvideo befindet.
Wie kann ich dies konvertieren, um es rekursiv auszuführen? Wenn ich benutze
find
, wo soll die Variable deklariert werden$file
? Und wo solltest du deklarieren$target
? Sind allefind
nur wirklich Einzeiler? Ich muss die Datei wirklich an eine Variable übergeben$file
, da ich die Bedingungsprüfung noch ausführen muss.Und unter der Annahme, dass (1) erfolgreich ist, wie kann sichergestellt werden, dass die Anforderung "Dann sollte die Ausgabe in demselben Ordner gespeichert werden, in dem sich das Originalvideo befindet" erfüllt ist?
**
Globing-Operator, mit dem rekursiv nach Dateien gesucht werden kann. Es ist nicht portabel, spielt aber gut mit der For-Loop-Syntax.Antworten:
Sie haben diesen Code:
welches im aktuellen Verzeichnis läuft. Um daraus einen rekursiven Prozess zu machen, haben Sie mehrere Möglichkeiten. Am einfachsten (IMO) ist es,
find
wie von Ihnen vorgeschlagen zu verwenden. Die Syntax fürfind
ist sehr "un-UNIX-ähnlich", aber das Prinzip hier ist, dass jedes Argument mit UND- oder ODER-Bedingungen angewendet werden kann. Hier werden wir sagen " Wenn dieser Dateiname mit ODER dieser Dateiname übereinstimmt, dann drucke ihn aus ". Die Dateinamen Muster zitiert werden , so dass der Schal nicht halten , sie bekommen kann (denken Sie daran , dass die Schale für den Ausbau aller nicht notierten Muster verantwortlich ist, also , wenn Sie ein nicht notierte Muster hatten*.mp4
und man mußtejaneeyre.mp4
in Ihrem aktuellen Verzeichnis, würde die Schale ersetzen*.mp4
mit das Spiel, undfind
würde-name janeeyre.mp4
statt Ihrer gewünschten sehen-name *.mp4
, es wird schlimmer, wenn*.mp4
stimmt mit mehreren Namen überein ...). Den Klammern wird auch ein Präfix vorangestellt\
, damit die Shell nicht versucht, sie als Unterschalenmarkierungen zu verwenden (wir könnten stattdessen die Klammern zitieren, falls dies bevorzugt wird :)'('
.Die Ausgabe davon muss in die Eingabe einer
while
Schleife eingespeist werden, die jede Datei der Reihe nach verarbeitet:Jetzt müssen nur noch die beiden Teile mit einer Pipe verbunden werden,
|
sodass der Ausgang vonfind
zum Eingang derwhile
Schleife wird.Während Sie diesen Code zu test würde ich empfehlen , Sie beide Präfix
ffmpeg
undrm
mitecho
so können Sie sehen , was würde ausgeführt werden - und mit welchen Pfaden.Hier ist das Endergebnis, einschließlich der
echo
Aussagen, die ich zum Testen empfehle:quelle
echo *
in einem leeren Verzeichnis. Versuchen Sie es dann in einem nicht leeren Verzeichnis. Ersteres druckt*
und letzteres ersetzt den Stern durch eine Liste von Dateien. Das gleiche passiert mitfind
.xargs
kann ein Minenfeld sein. Sei sehr vorsichtig damit.xargs
undfind
ohne-0
Option, haben Sie immer noch Probleme. Lassen Siefind
die ganze Arbeit mit-exec
ist bessere Lösung.-print0
die anderen professionellen Ausstattungen zu verwenden, erhalten Sie zwar solideren Code, aber mehr Komplexität, die erklärt werden muss.Mit POSIX finden Sie:
Ersetzen Sie
echo
durch einen beliebigen Befehl, den Sie verwenden möchten.Wenn Sie GNU find oder BSD find haben, können Sie verwenden
-regex
:quelle
find . \( -name '*.mkv' -o -name '*avi' -o -name '*mp4' -o -name '*flv' -o \ -name '*ogg' -o -name '*mov' \) -exec sh -c '
am oberen Rand des Snippets nach unten geleitet wirdfile
. und was bedeutetsh {} +
? Vielen Dank!find . -regex '.*\.\(mkv\|avi\|mp4\|flv\|ogg\|mov\)'
Beispiel-Snippet ohne Piping (vorausgesetzt, Sie geben den Pfad als Argument an):
Die Schale liest die
IFS
Variable, die (festgelegt istspace
,tab
,newline
) standardmäßig. Dann wird jedes Zeichen in der Ausgabe von betrachtetfind
. Wenn es alsospace
denkt, dass es das Ende des Dateinamens ist (eine Datei mit Leerzeichen, zum Beispiel "Sin City.avi", wird als zwei Dateien "Sin" und "City.avi" behandelt). Mit IFS = $ '\ n' sagen wir also, dass die Eingabe aufgeteilt werden sollnewlines
. Und schließlich stellen wir alt (Standard) wieder her,IFS
das in$OIFS
Variablen gespeichert ist.Oder wie in Kommentaren vorgeschlagen, könnte ein besserer Ansatz sein:
quelle
OIFS
undIFS
am Anfang und am Ende des Skripts nicht verstanden. und wenn ich richtig verstehe, wird der Pfad bei var deklariert$1
? und dasbackup_dir
ist nur ein Backup zum Debuggen, oder?local IFS=
, die dies tut . Daher ist IFS nur für den Kontext Ihrer Funktion leer und muss nicht gespeichert / wiederhergestellt werden.Willkommen bei Unix :)
Um einige Ihrer Nebenfragen zu beantworten, die in den Antworten auf die Hauptfrage nicht behandelt wurden:
Shell-Skripte haben sicherlich einige Ecken und Kanten, da bei Dateinamen mit Leerzeichen viele Dinge kaputt gehen. Und fast alles bricht bei Dateinamen mit Zeilenumbrüchen ab (zum Glück macht niemand diese absichtlich). Dateinamen mit Glob-Zeichen wie
[
,]
und*
sind manchmal auch ein Problem. Manchmal lohnt es sich einfach nicht, schwer lesbaren Shell-Code zu schreiben, der den Standards von Wooledges BashGuide entspricht , für den eigenen Gebrauch oder für einen einmaligen Vorgang , bei dem Sie wissen, dass Ihre Dateinamen nicht seltsam sind.Shell-Variablen müssen nicht deklariert werden. In Bash können Sie die
shopt -o nounset
Referenz- und UnSET-Variable zu einem Fehler machen, aber das ist nicht ganz dasselbe wie nicht deklariert. Das Deaktivieren einer Variablen kann hilfreich sein. In einer Shell-Funktion empfiehlt es sich, alle Ihre Provisorien mit zu deklarierenlocal foo bar baz;
, damit Sie die Shell-Umgebung nicht mit Variablen verunreinigen oder, schlimmer noch, auf die gleichnamige Variable des Aufrufers treten.Bei der Arbeit mit der Shell werden viele Daten übergeben, indem die Daten auf stdout gedruckt werden. Pipes senden diese Daten an ein anderes Programm, das sie auf stdin liest (und normalerweise etwas auf stdout druckt). Sie können die Ausgabe in Shell-Variablen mithilfe der Befehlssubstitution erfassen
$()
. zBfor i in $( locate foo | grep bar );do echo "$i"; done
. (Dies wird bei Dateinamen mit Leerzeichen unterbrochen, wie z. B. viel Shell-Code, wenn Sie nicht vorsichtig sind. Verwendenread
Sie diese Option, wenn Sie zuverlässige Skripts schreiben möchten.)locate
Druckt,grep
liest und druckt, und die Shell liest die Ausgabe vongrep
. (Die Shell erhält die Ausgabe von,grep
indem sie grep mit ihrer Ausgabe startet, die mit der Eingabeseite einer von der Shell erstellten Pipe verbunden ist. Die Shell liest die Ausgabeseite der Pipe.)Eine Pipe ist nur eine Möglichkeit für Programme, so zu arbeiten, als würden sie in eine Datei schreiben, aber tatsächlich schreiben sie in einen kleinen Puffer. Bei einem Prozess, der aus einer Pipe liest, wird der
read(2)
Systemaufruf zurückgegeben, wenn Daten verfügbar sind. Dies geschieht nur, wenn etwas an das andere Ende der Pipe geschrieben wird.Die Schale ist
|
,$()
und einige andere Syntaxelemente sind , wie Sie die Schale sagen , wie die Sanitär - Verbindungsprogramme miteinander zu gründen, und an der Schale.Es ist leicht, schlechte Redewendungen für die Shell-Programmierung zu lernen, da viele der offensichtlichen Dinge und alten Methoden versteckte Fallstricke aufweisen, die bei ungeraden Dateinamen auftreten. Siehe zum Beispiel http://mywiki.wooledge.org/BashFAQ/001 .
Es ist besser, von Anfang an sichere Methoden zum Erstellen von Skripten zu erlernen, als Methoden zu lernen, die bei ungeraden Dateinamen brechen, solange sie nicht zu klobig zum Tippen sind. :) :)
Viele GNU-Utils haben die Option -0, um ASCII NUL (das 0-Byte kann in Dateinamen oder Text nicht vorhanden sein) als Datensatztrennzeichen zu verwenden. Auf diese Weise können Sie Daten zwischen
find
undsort
beispielsweise weiterleiten, ohne dass eine "Zeile" der Suchausgabe in mehrere Zeilen der Sortiereingabe umgewandelt werden kann. Dies ist nicht besonders nützlich, wenn Sie die Daten in eine Shell-Variable übertragen möchten, da bash keine Möglichkeit hat,\0
begrenzte Zeilen zu lesen . (Ich denke nicht, dass dies ein gültiger Wert für IFS ist.)Auf jeden Fall ist es der Grund, zu vermeiden, dass die Shell Daten als Code behandelt, alles, was Sie können, immer in doppelten Anführungszeichen zu setzen, es sei denn, Sie möchten wirklich eine Wortteilung. Wenn Sie jemals möchten, dass Ihr Gehirn beim Betrachten von komplexem Shell-Code verletzt wird, schauen Sie sich einfach den Bash-Completion-Code an. (Es behandelt die programmierbare Vervollständigung, die clevere Dinge wie das Vervollständigen
ls --colo => --color
oder nur das Vervollständigen von * .zip-Dateien zum Entpacken erledigt.)set -x
Und drücken Sie die Tabulatortaste: P. (Setzen Sie + x, um die Ausführungsverfolgung zu deaktivieren.)re: your for loop: Mit
*.mkv
einem Ihrer Muster haben Sie source = dest für diese Eingabedateien.ffmpeg
fordert Sie auf, die Ausgabedatei für jede Datei zu überschreiben.Müssen Sie das Audio auch wirklich transkodieren?
-c:a copy
könnte eine gute Idee sein. Die Videobitrate ist normalerweise eine größere Sache. Und Sie möchten möglicherweise-preset slow
(oderslower
sogarveryslow
) verwenden, um mehr Qualität pro Bitrate zu erzielen, und zwar auf Kosten einer höheren CPU-Auslastung. Es gibt auch-crf 20
(Standard 23). https://trac.ffmpeg.org/wiki/Encode/H.264 . Sie haben das hoffentlich schon gewusst und es-c:v libx264
weggelassen, weil es für das Bash-Scripting nicht relevant war, aber nur für den Fall ...: P ist die Standardeinstellung bei der Ausgabe auf mkv, also ist das gut.quelle