Welcome to Unix :)
To answer some of your minor questions that answers to the main question didn't cover:
Shell scripting certainly has some rough edges, since a lot of things break on file names with spaces. And almost everything breaks on filenames with newlines (fortunately, nobody makes those on purpose). Filenames containing glob characters like [
, ]
, and *
are sometimes a problem, too. Sometimes it's just not worth writing hard-to-read shell code up to the standards of wooledge's BashGuide, for your own use, or for a one-off where you know your filenames aren't weird.
where to declare the variable:
Shell variables don't need to be declared. In bash, you can shopt -o nounset
to make it an error to reference and unSET variable, but that's not quite the same thing as undeclared. Unsetting a variable can be useful. In a shell function, it is good practice to declare all your temporaries with local foo bar baz;
, so you don't litter the shell environment with variables, or worse, step on the caller's variable of the same name.
I barely understand "piping".
When working with the shell, a lot of data passing happens by printing the data to stdout. Pipes send that data to another program, which reads it on stdin (and usually prints something on stdout). You can capture output into shell variables using command substitution, $()
. e.g. for i in $( locate foo | grep bar );do echo "$i"; done
. (this will break on filenames with spaces in them, like a lot of shell code if you aren't careful. Use read
if you want to write scripts that are reliable.) locate
prints, grep
reads and prints, and the shell reads the output of grep
. (The shell gets its hands on the output of grep
by starting grep with its output connected to the input side of a pipe which the shell created. The shell reads the output side of the pipe.)
A pipe is just a way for programs to work like they're writing to a file, but actually they're writing into a small buffer. A process reading from a pipe will have its read(2)
system call return when there's data available, which only happens when something writes to the other end of the pipe.
The shell's |
, $()
, and some other syntax elements are how you tell the shell how to set up the plumbing connecting programs to each other, and to the shell.
It's easy to learn bad idioms for shell programming, because a lot of the obvious things and old ways of doing things have hidden pitfalls which break on odd filenames. See for example http://mywiki.wooledge.org/BashFAQ/001.
Better to learn safe ways of scripting from the start, rather than learning ways that break on odd filenames, as long as they're not too clunky to type. :)
A lot of GNU utils have a -0 option, to use ASCII NUL (the 0 byte, can't be present in filenames or text) as a record separator. This lets you pipe data between find
and sort
, for example, without any possibility of having one "line" of find output turned into multiple lines of sort input. This ends up not being super useful when you want to get the data into a shell variable, because bash doesn't have a way to read \0
-delimited lines. (I don't think that's a valid value for IFS.)
Anyway, avoiding having the shell treat data as code is the reason to always double-quote everything you can, unless you really WANT word-splitting. If you ever want to make your brain hurt looking at complex shell code, just look at the bash-completion code. (It handles the programmable completion that does clever things like completing ls --colo => --color
, or only completing *.zip files for unzip.) set -x
and hit tab :P. (set +x to turn off execution tracing.)
re: your for loop:
with *.mkv
as one of your patterns, you're going to have source = dest for those input files. ffmpeg
will prompt you to overwrite the output file for each one.
Also, do you really need to transcode the audio? -c:a copy
might be a good idea. Video bitrate is usually a bigger deal. And you might want to use -preset slow
(or slower
, or even veryslow
) to get more quality per bitrate, at the cost of more CPU usage. There's also -crf 20
(default 23). https://trac.ffmpeg.org/wiki/Encode/H.264. You hopefully already knew this, and left it out because it wasn't relevant to the bash scripting, but just in case... :P -c:v libx264
is the default when outputting to mkv, so that's good.
**
globing operator to search for files recursively. Its not portable but it plays nice with the for-loop syntax.