diff options
author | Thomas Voss <mail@thomasvoss.com> | 2023-08-15 14:57:32 +0200 |
---|---|---|
committer | Thomas Voss <mail@thomasvoss.com> | 2023-08-15 14:57:32 +0200 |
commit | d5635e946e9df6f519ec8cf08cebfc35dbe6c788 (patch) | |
tree | 46893cffdf23a2b15f8b7839c69d5df2bcbb8bca /src | |
parent | cfa35dcb2d332977e80a5811b6d42e9949bd4814 (diff) |
Add a post on ‘mmv’
Diffstat (limited to 'src')
29 files changed, 830 insertions, 10 deletions
diff --git a/src/prj/mmv/camel-to-snake-naïve.sh.html b/src/prj/mmv/camel-to-snake-naïve.sh.html new file mode 100644 index 0000000..573da41 --- /dev/null +++ b/src/prj/mmv/camel-to-snake-naïve.sh.html @@ -0,0 +1,2 @@ +<span class="sh-cmt"># If you aren’t a shell-guru, take a moment to figure out how this works!</span> +$ <span class="sh-fn">ls</span> *.[ch] | <span class="sh-fn">sed</span> <span class="sh-str">'p; s/[A-Z]/\L_&/g'</span> | <span class="sh-fn">xargs</span> -L2 mv diff --git a/src/prj/mmv/camel-to-snake-smart.sh.html b/src/prj/mmv/camel-to-snake-smart.sh.html new file mode 100644 index 0000000..191e87f --- /dev/null +++ b/src/prj/mmv/camel-to-snake-smart.sh.html @@ -0,0 +1 @@ +$ <span class="sh-fn">ls</span> *.[ch] | <span class="sh-fn">mmv</span> sed <span class="sh-str">'s/[A-Z]/\L_&/g'</span> diff --git a/src/prj/mmv/conflict.dot b/src/prj/mmv/conflict.dot new file mode 100644 index 0000000..b2927a2 --- /dev/null +++ b/src/prj/mmv/conflict.dot @@ -0,0 +1,26 @@ +digraph { + bgcolor = "#00000000" + + graph[rankdir=LR] + node[ + shape=rect, + fontname="Iosevka Smooth", + color="#C5C8C6", + fontcolor="#C5C8C6" + ] + edge[color="#C5C8C6"] + + n0[label="foo\nbar\nbaz"] + n1[label="foo\nbar\nbaz"] + n2[label="rev"] + n3[label="rev"] + n4[label="oof\nrab\nzab"] + n5[label="oof\nrab\nzab"] + n6[label="mmv"] + n7[label="mmv"] + n8[label="?", shape=circle] + n9[label="?", shape=circle] + + "ls foo$'\\n'bar baz" -> n1 -> n3 -> n5 -> n7 -> n9 + "ls foo bar$'\\n'baz" -> n0 -> n2 -> n4 -> n6 -> n8 +} diff --git a/src/prj/mmv/e-flag.dot b/src/prj/mmv/e-flag.dot new file mode 100644 index 0000000..18937fa --- /dev/null +++ b/src/prj/mmv/e-flag.dot @@ -0,0 +1,21 @@ +digraph { + bgcolor = "#00000000" + + graph[rankdir=LR] + node[ + shape=rect, + fontname="Iosevka Smooth", + color="#C5C8C6", + fontcolor="#C5C8C6" + ] + edge[color="#C5C8C6"] + + ls[label="ls foo$'\\n'bar baz"] + mmv1[label="mmv -e"] + mmv2[label="mmv -e"] + mmv_out[label="foo\\nbar\nbaz"] + vipe_out[label="bar\\nfoo\nbaz"] + final[label="bar$'\\n'foo\nbaz"] + + ls -> mmv1 -> mmv_out -> vipe -> vipe_out -> mmv2 -> final +} diff --git a/src/prj/mmv/examples/camel-to-snake.sh.html b/src/prj/mmv/examples/camel-to-snake.sh.html new file mode 100644 index 0000000..bd67492 --- /dev/null +++ b/src/prj/mmv/examples/camel-to-snake.sh.html @@ -0,0 +1 @@ +$ <span class="sh-fn">git</span> ls-files <span class="sh-str">'*.[ch]'</span> | <span class="sh-fn">mmv</span> sed <span class="sh-str">'s/[A-Z]/\L_&/g'</span> diff --git a/src/prj/mmv/examples/hyphens.sh.html b/src/prj/mmv/examples/hyphens.sh.html new file mode 100644 index 0000000..ca49946 --- /dev/null +++ b/src/prj/mmv/examples/hyphens.sh.html @@ -0,0 +1 @@ +$ <span class="sh-fn">ls</span> | <span class="sh-fn">mmv</span> tr ' ' '-' diff --git a/src/prj/mmv/examples/i-flag.sh.html b/src/prj/mmv/examples/i-flag.sh.html new file mode 100644 index 0000000..c22c7c9 --- /dev/null +++ b/src/prj/mmv/examples/i-flag.sh.html @@ -0,0 +1 @@ +$ <span class="sh-fn">ls</span> --zero | <span class="sh-fn">mmv</span> -0i cmd diff --git a/src/prj/mmv/examples/lowercase.sh.html b/src/prj/mmv/examples/lowercase.sh.html new file mode 100644 index 0000000..84abb92 --- /dev/null +++ b/src/prj/mmv/examples/lowercase.sh.html @@ -0,0 +1 @@ +$ <span class="sh-fn">find</span> . -print0 | <span class="sh-fn">mmv</span> -0 tr A-Z a-z diff --git a/src/prj/mmv/examples/number.sh.html b/src/prj/mmv/examples/number.sh.html new file mode 100644 index 0000000..5e8e74a --- /dev/null +++ b/src/prj/mmv/examples/number.sh.html @@ -0,0 +1,2 @@ +$ <span class="sh-fn">ls</span> <span class="sh-str">'The Fellowship of the Ring.mp4'</span> … <span class="sh-str">'The Two Towers.mp4'</span> | \ + <span class="sh-fn">mmv</span> awk <span class="sh-str">'{ gsub(" ", "-"); printf "%02d-%s", NR, tolower($0) }'</span> diff --git a/src/prj/mmv/examples/swap.sh.html b/src/prj/mmv/examples/swap.sh.html new file mode 100644 index 0000000..02c9c28 --- /dev/null +++ b/src/prj/mmv/examples/swap.sh.html @@ -0,0 +1 @@ +$ <span class="sh-fn">ls</span> foo bar | <span class="sh-fn">mmv</span> tac diff --git a/src/prj/mmv/examples/vipe.sh.html b/src/prj/mmv/examples/vipe.sh.html new file mode 100644 index 0000000..933039a --- /dev/null +++ b/src/prj/mmv/examples/vipe.sh.html @@ -0,0 +1 @@ +$ <span class="sh-fn">ls</span> | <span class="sh-fn">mmv</span> -0e vipe diff --git a/src/prj/mmv/index.html b/src/prj/mmv/index.html new file mode 100644 index 0000000..d13f7c8 --- /dev/null +++ b/src/prj/mmv/index.html @@ -0,0 +1,658 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + m4_include(head.html) + </head> + <body> + <header> + <div> + <h1>Moving Files the Right Way</h1> + m4_include(nav.html) + </div> + + <figure class="quote"> + <blockquote> + <p>I think the OpenBSD crowd is a bunch of masturbating + monkeys, in that they make such a big deal about + concentrating on security to the point where they pretty much + admit that nothing else matters to them.</p> + </blockquote> + <figcaption> + Linux Torvalds + </figcaption> + </figure> + </header> + + <main> + <p> + <em> + You can find the <code>mmv</code> git repository over at + <a href="https://git.sr.ht/~mango/mmv" + target="_blank">sourcehut</a> + or <a href="https://github.com/Mango0x45/mmv">GitHub</a>. + </em> + </p> + + <h2>Table of Contents</h2> + + <ul> + <li><a href="#prologue">Prologue</a></li> + <li><a href="#moving">Advanced Moving and Pitfalls</a></li> + <li><a href="#mapping">Name Mapping with <code>mmv</code></a></li> + <li><a href="#newlines">Filenames with Embedded Newlines</a></li> + <ul> + <li><a href="#0-flag">The Simple Case</a></li> + <li><a href="#e-flag">Encoding Newlines</a></li> + </ul> + <li><a href="#i-flag">Individual Execution</a></li> + <li><a href="#safety">Safety</a></li> + <li><a href="#examples">Examples</a></li> + </ul> + + <h2 id="prologue">Prologue</h2> + <p> + File moving and renaming is one of the most common tasks we + undertake on the command-line. We basically always do this with + the <code>mv</code> utility, and it gets the job done most of the + time. Want to rename one file? Use <code>mv</code>! Want to + move a bunch of files into a directory? Use <code>mv</code>! + How could mv ever go wrong? Well I’m glad you asked! + </p> + + <h2 id="moving">Advanced Moving and Pitfalls</h2> + <p> + Let’s start off nice and simple. You just inherited a C project + that uses the sacrilegious + <a + href="https://en.wikipedia.org/wiki/Camel_case" + target="_blank" + >camelCase</a> + naming convention for its files: + </p> + + <figure> + <pre>m4_fmt_code(ls-files.sh.html)</pre> + </figure> + + <p> + This deeply upsets you, as it upsets me. So you decide you want + to switch all these files to use + <a + href="https://en.wikipedia.org/wiki/Snake_case" + target="_blank" + >snake_case</a>, + like a normal person. Well how would you do this? You use + <code>mv</code>! This is what you might end up doing: + </p> + + <figure> + <pre>m4_fmt_code(manual-mv.sh.html)</pre> + </figure> + + <p> + Well… it works I guess, but it’s a pretty shitty way of renaming + these files. Luckily we only had 5, but what if this was a much + larger project with many more files to rename? Things would get + tedious. So instead we can use a pipeline for + this: + </p> + + <figure> + <pre>m4_fmt_code(camel-to-snake-naïve.sh.html)</pre> + </figure> + + <aside> + <p> + The given example assumes your <code>sed</code> + implementation supports ‘<code>\L</code>’ which is a + non-standard <abbr class="gnu">GNU</abbr> extension. + </p> + </aside> + + <p> + That works and it gets the job done, but it’s not really ideal is + it? There are a couple of issues with this. + </p> + + <ol> + <li> + <p> + You’re writing more complicated code. This has the + obvious drawback of potentially being more error-prone, + but also risks taking more time to write than you’d like + as you might have forgotten if <code>xargs</code> + actually has an ‘<code>-L</code>’ option or not (which + would require reading the + <a href="https://www.man7.org/linux/man-pages/man1/xargs.1.html" + target="_blank" ><code>xargs(1)</code></a> manual). + </p> + </li> + <li> + <p> + If you try to rename the file <em>foo</em> + to <em>bar</em> but <em>bar</em> already exists, you end + up deleting a file you may not have wanted to. + </p> + </li> + <li> + <p> + In a similar vein to the previous point, you need to be + very careful about schemes like renaming the + file <em>a</em> to <em>b</em> and <em>b</em> + to <em>c</em>. You run the risk of turning <em>a</em> + into <em>c</em> and losing the file <em>b</em> entirely. + </p> + </li> + <li> + <p> + Moving symbolic links is its own whole can of worms. If + a symlink points to a relative location then you need to + make sure you keep pointing to the right place. If the + symlink is absolute however then you can leave it + untouched. But what if the symlink points to a file + that you’re moving as part of your batch move operation? + Now you need to handle that too. + </p> + </li> + </ol> + + <h2 id="mapping">Name Mapping with <code>mmv</code></h2> + + <p> + What is <code>mmv</code>? It’s the solution to all your + problems, that’s what it is! <code>mmv</code> takes as its + argument(s) a utility and that utilities arguments and uses that + to create a mapping between old and new filenames — similar to + the <code>map()</code> function found in many programming + languages. I think to best convey how the tool functions, I + should provide an example. Let’s try to do the same thing we did + previously where we tried to turn camelCase files to snake_case, + but using <code>mmv</code>: + </p> + + <figure> + <pre>m4_fmt_code(camel-to-snake-smart.sh.html)</pre> + </figure> + + <p>Let me break down how this works.</p> + + <p> + <code>mmv</code> starts by reading a series of filenames + separated by newlines from the standard input. Yes, sometimes + filenames have newlines in them and yes there is a way to handle + them but I shall get to that later. The filenames that + <code>mmv</code> reads from the standard input will be referred + to as the <em>input files</em>. Once all the input files have + been read, the utility specified by the arguments is spawned; in + this case that would be <code>sed</code> with the argument + <code>'s/[A-Z]/\L_&/g'</code>. The input files are then piped + into <code>sed</code> the exact same way that they would have + been if we ran the above commands without <code>mmv</code>, and + the output of <code>sed</code> then forms what will be referred + to as the <em>output files</em>. Once a complete list of output + files is accumulated, each input file gets renamed to its + corresponding output file. + </p> + + <p> + Let’s look at a simpler example. Say we want to rename 2 files + in the current directory to use lowercase letters, we could use + the following command: + </p> + + <figure> + <pre>m4_fmt_code(mmv-tr.sh.html)</pre> + </figure> + + <p> + In the above example <code>mmv</code> reads 2 lines from + standard input, those being <em>LICENSE</em> + and <em>README</em>. Those are our 2 input files now. + The <code>tr</code> utility is then spawned and the input files + are piped into it. We can simulate this in the shell: + </p> + + <figure> + <pre>m4_fmt_code(tr.sh.html)</pre> + </figure> + + <p> + As you can see above, <code>tr</code> has produced 2 lines of + output; these are our 2 output files. Since we now have our 2 + input files and 2 output files, <code>mmv</code> can go ahead + and rename the files. In this case it will rename + <em>LICENSE</em> to <em>license</em> and + <em>README</em> to <em>readme</em>. For some examples, check + the <a href="#examples">examples</a> section of this page down + below. + </p> + + <h2 id="newlines">Filenames with Embedded Newlines</h2> + + <p> + People are retarded, and as a result we have filenames with + newlines in them. All it would have taken to solve this issue + for everyone was for literally <strong>anybody</strong> during + the early UNIX days to go “<em>hey, this is a bad idea!</em>”, + but alas, we must deal with this. Newlines are of course not + the only special characters filenames can contain, but they are + the single most infuriating to deal with; the UNIX utilities all + being line-oriented really doesn’t work well with these files. + </p> + + <p> + So how does <code>mmv</code> deal with special characters, and + newlines in particular? Well it does so by providing the user + with the <code>-0</code> and <code>-e</code> flags: + </p> + + <dl> + <dt><code>-0</code></dt> + <dd> + <p> + Tell <code>mmv</code> to expect its input to not be + separated by newlines (‘<code>\n</code>’), but by NUL + bytes (‘<code>\0</code>’). NUL bytes are the only + characters not allowed in filenames besides forward + slashes, so they are an obvious choice for an + alternative separator. + </p> + </dd> + <dt><code>-e</code></dt> + <dd> + <p> + Encode newlines in filenames before passing them to the + provided utility. Newline characters are replaced by the + literal string ‘<code>\n</code>’ and backslashes by the + literal string ‘<code>\\</code>’. After processing, the + resulting output is decoded again. + </p> + <p> + If combined with the <code>-0</code> flag, then while + input will be read assuming a NUL-byte input-seperator, + the encoded input files will be written to the spawned + process newline-seperated. + </p> + </dd> + </dl> + + <h3 id="0-flag">The Simple Case</h3> + + <p> + In order to better understand these flags and how they work + let’s go though another example. We have 2 files — one with and + one without an embedded newline — and our goal is to simply + reverse these filenames. In this example I am going to be + displaying newlines in filenames with the “<code>$'\n'</code>” + syntax as this is how my shell displays embedded newlines. + </p> + + <p> + We can start by just trying to naïvely pass these 2 files + to <code>mmv</code> and use <code>rev</code> to reverse the + names, but this doesn’t work: + </p> + + <figure> + <pre>m4_fmt_code(mmv-rev.sh.html)</pre> + </figure> + + <p> + The reason this doesn’t work is because due to the line-oriented + nature of <code>ls</code> and <code>rev</code>, we are actually + trying to rename the files <em>foo</em>, <em>bar</em>, and + <em>baz</em> to the new filenames <em>zab</em>, + <em>rab</em>, and <em>oof</em>. As can be seen in the following + diagram, the embedded newline is causing our input to be ambiguous + and <code>mmv</code> can’t reliably proceed + anymore <x-ref>1</x-ref>: + </p> + + <figure> + <object data="conflict.svg" type="image/svg+xml"></object> + </figure> + + <aside> + <p data-ref="1"> + The reason you get a cryptic “file not found” error message + is because <code>mmv</code> tries to assert that all the + input files actually exist before doing anything. Since + “foo” isn’t a real file, we error out. + </p> + </aside> + + <p> + The first thing we need to do in order to proceed is to pass + the <code>-0</code> flag to <code>mmv</code>. This will + tell <code>mmv</code> that we want to use the NUL-byte as our + input separator and not the newline. We also need <code>ls</code> + to actually provide us with the filenames delimited by NUL-bytes. + Luckily <abbr class="gnu">GNU</abbr> <code>ls</code> gives us the + <code>--zero</code> flag to do just that: + </p> + + <figure> + <pre>m4_fmt_code(mmv-rev-zero.sh.html)</pre> + </figure> + + <p> + So we’re getting places, but we aren’t quite there yet. The + issue we’re getting now is that <code>mmv</code> recieved 2 + input files from the standard input, but <code>rev</code> + produced 3 output files. Why is that? Well let’s try our hand + at a little bit of command-line debugging with <code>sed</code>: + </p> + + <figure> + <pre>m4_fmt_code(sed-debugging.sh.html)</pre> + </figure> + + <p> + If you aren’t quite sure what the above is doing, here’s a quick + summary: + </p> + + <ul> + <li> + The <code>-U</code> flag given to <code>ls</code> tells it + not to sort our output. This is purely just to keep this + example clear to the reader. + </li> + <li> + The <code>-n</code> flag given to <code>sed</code> tells it + not to print the input line automatically at the end of the + provided script. + </li> + <li> + The <code>l</code> command in <code>sed</code> prints the + current input in a “visually unambiguous form”. + </li> + </ul> + + <p> + In the <code>sed</code> output, we can see that <samp>$</samp> + represents the end of a line, and <samp>\000</samp> represents + the NUL-byte. All looks good here, we have two inputs seperated + by NUL-bytes. Now let’s try to throw in <code>rev</code>: + </p> + + <figure> + <pre>m4_fmt_code(sed-debugging-rev.sh.html)</pre> + </figure> + + <p> + Well wouldn’t you know it? Since <code>rev</code> <em>also</em> + works with newline-seperated input, it reversed out NUL-byte + seperators and now gives us 3 outputs. Luckily the folks over + at <em>util-linux</em> provided us with the <code>-0</code> flag + here too, so that we can properly handle NUL-delimited input. + Combining all of this together we get a final working product: + </p> + + <figure> + <pre>m4_fmt_code(reverse-embedded-newline.sh.html)</pre> + </figure> + + <h3 id="e-flag">Encoding Newlines</h3> + + <p> + Sometimes we want to rename a bunch of files, but the command we + want to use doesn’t support NUL-bytes as nicely as we would + like. In these cases, you may want to consider encoding your + newline characters into the literal string ‘<code>\n</code>’ and + then passing your input newline-seperated to your given command + with the <code>-e</code> flag. + </p> + + <p> + For a real-world example, perhaps you want to edit some + filenames in vim, or whatever other editor you use. Well we can + do this incredibly easily with the <code>vipe</code> utility + from + the <a href="https://joeyh.name/code/moreutils/">moreutils</a> + collection. The <code>vipe</code> command simply reads input + from the standard input, opens it up in your editor, and then + prints the resulting output to the standard output; perfect + for <code>mmv</code>! We do not really want to deal with + NUL-bytes in our text-editor though, so let’s just encode our + newlines: + </p> + + <figure> + <pre>m4_fmt_code(vipe.sh.html)</pre> + </figure> + + <aside> + <p> + Notice how you still need to pass the <code>-0</code> flag + to <code>mmv</code> know that our inputfiles may have + embedded newlines. + </p> + </aside> + + <p> + When running the above code example, you will see the following + in your editor: + </p> + + <figure> + <pre>m4_fmt_code(vim.html)</pre> + </figure> + + <p> + After you exit your editor, <code>mmv</code> will decode all + occurances of ‘<code>\n</code>’ back into a newline, and all + occurances of ‘<code>\\</code>’ back into a backslash: + </p> + + <figure> + <object data="e-flag.svg" type="image/svg+xml"></object> + </figure> + + <h2 id="i-flag">Individual Execution</h2> + <p> + The previous examples are great and all, but what do you do if + your mapping command doesn’t have the concept of an input + seperator at all? This is where the <code>-i</code> flag comes + into play. With the <code>-i</code> flag we can + get <code>mmv</code> to execute our mapping command for every + input filename. This means that as long as we can work with a + complete buffer, we don’t need to worry about seperators. + </p> + + <p> + To be honest, I cannot really think of any situation where you + might actually need to do this. If you can think of one, + please <a href="mailto:mail@thomasvoss.com">email me</a> and + I’ll update the example on this page. Regardless, let’s imagine + that we wanted to rename some files so that their filenames are + replaced with their filename + <a href="https://en.wikipedia.org/wiki/SHA-1" target="_blank"> + SHA-1 hash</a>. + On Linux we have the <code>sha1sum</code> program which reads + input from the standard input and outputs the SHA-1 hash. This + is how we would use it with <code>mmv</code>: + </p> + + <figure> + <pre>m4_fmt_code(sha1sum-long-example.sh.html)</pre> + </figure> + + <p> + Another approach is to invoke <code>mmv</code> twice: + </p> + + <figure> + <pre>m4_fmt_code(sha1sum-short-example.sh.html)</pre> + </figure> + + <p> + If you are confused about why we need to make a call + to <code>awk</code>, it’s because the <code>sha1sum</code> + program outputs 2 columns of data. The first column is our hash + and the second column is the filename where the to-be-hashed + data was read from. We don’t want the second column. + </p> + + <p> + Unlike in previous examples where one process was spawned to map + all our filenames, with the <code>-i</code> flag we are spawning + a new instance for each filename. If you struggle to visualize + this, perhaps the following diagrams help: + </p> + + <figure> + <figcaption>Invoking <code>mmv</code> without <code>-i</code></figcaption> + <object data="without-i-flag.svg" type="image/svg+xml"></object> + </figure> + + <figure> + <figcaption>Invoking <code>mmv</code> with <code>-i</code></figcaption> + <object data="with-i-flag.svg" type="image/svg+xml"></object> + </figure> + + <h2 id="safety">Safety</h2> + <p> + When compared to the standard <code>for f in *; do mv $f …; + done</code> or <code>ls | … | xargs -L2 mv</code> + constructs, <code>mmv</code> is significantly more safe to use. + These are some of the safety features that are built into the + tool: + </p> + + <ol> + <li> + If the number of input- and output files differs, execution + is aborted before making any changes. + </li> + <li> + If an input file is renamed to the name of another input + file, the second input file is not lost (i.e. you can rename + <em>a</em> to <em>b</em> and <em>b</em> to <em>a</em> with + no problem). + </li> + <li> + All input files must be unique and all output files must be + unique. Otherwise execution is aborted before making any + changes. + </li> + <li> + In the case that something goes wrong during execution + (perhaps you tried to move a file to a non-existant + directory, or a syscall failed), a backup of your input + files is saved automatically by <code>mmv</code> for + recovery. + </li> + </ol> + + <p> + Due to the way <code>mmv</code> handles #2, when things do go + wrong you may find that all of your input files have + disappeared. Don’t worry though, <code>mmv</code> takes a + backup of your code before doing anything. If you + run <code>mmv</code> with the <code>-v</code> option for verbose + output, you’ll notice it backing up your stuff in + the <code>$XDG_CACHE_DIR</code> directory: + </p> + + <figure> + <pre>m4_fmt_code(mmv-verbose.sh.html)</pre> + </figure> + + <p> + Upon successful execution + the <code>$XDG_CACHE_DIR/mmv/TIMESTAMP</code> directory will be + automatically removed, but it remains when things go wrong so + that you can recover any missing data. The names of the + backup-subdirectories in the <code>$XDG_CACHE_DIR/mmv</code> + directory are timestamps of when the directories were created. + This should make it easier for you to figure out which directory + you need to recover if you happen to have multiple of these. + </p> + + <h2 id="examples">Examples</h2> + + <aside> + <p> + All of these examples are ripped straight from + the <code>mmv(1)</code> manual page. If you + installed <code>mmv</code> through a package manager or + via <code>make install</code> then you should have the + manual installed on your system. + </p> + </aside> + + <p>Swap the files <em>foo</em> and <em>bar</em>:</p> + <figure> + <pre>m4_fmt_code(examples/swap.sh.html)</pre> + </figure> + + <p> + Rename all files in the current directory to use hyphens (‘-’) + instead of spaces: + </p> + <figure> + <pre>m4_fmt_code(examples/hyphens.sh.html)</pre> + </figure> + + <p> + Rename a given list of movies to use lowercase letters and + hyphens instead of uppercase letters and spaces, and number them + so that they’re properly ordered in globs (e.g. rename <em>The + Return of the King.mp4</em> to + <em>02-the-return-of-the-king.mp4</em>): + </p> + <figure> + <pre>m4_fmt_code(examples/number.sh.html)</pre> + </figure> + + <p> + Rename files interactively in your editor while encoding newline + into the literal string ‘<code>\n</code>’, making use + of <code><a href="https://linux.die.net/man/1/vipe" + target="_blank">vipe(1)</a></code> from <em>moreutils</em>: + </p> + <figure> + <pre>m4_fmt_code(examples/vipe.sh.html)</pre> + </figure> + + <p> + Rename all C source code- and header files in a git repository + to use snake_case instead of camelCase using + the <abbr class="gnu">GNU</abbr> + <code><a href="https://www.man7.org/linux/man-pages/man1/sed.1.html" + target="_blank">sed(1)</a></code> ‘<code>\n</code>’ extension: + </p> + <figure> + <pre>m4_fmt_code(examples/camel-to-snake.sh.html)</pre> + </figure> + + <p> + Lowercase all filenames within a directory hierarchy which may + contain newline characters: + </p> + <figure> + <pre>m4_fmt_code(examples/lowercase.sh.html)</pre> + </figure> + + <p> + Map filenames which may contain newlines in the current + directory with the command ‘<code>cmd</code>’, which itself does + not support nul-byte separated entries. This only works + assuming your mapping doesn’t require any context outside of the + given input filename (for example, you would not be able to + number your files as this requires knowledge of the input files + position in the input list): + </p> + <figure> + <pre>m4_fmt_code(examples/i-flag.sh.html)</pre> + </figure> + </main> + + <hr> + + <footer> + m4_footer + </footer> + </body> +</html> diff --git a/src/prj/mmv/ls-files.sh.html b/src/prj/mmv/ls-files.sh.html new file mode 100644 index 0000000..d24b5af --- /dev/null +++ b/src/prj/mmv/ls-files.sh.html @@ -0,0 +1,2 @@ +$ <span class="sh-fn">ls</span> +bytecodeVm.c fastLexer.c fastLexer.h slowParser.c slowParser.h diff --git a/src/prj/mmv/manual-mv.sh.html b/src/prj/mmv/manual-mv.sh.html new file mode 100644 index 0000000..2484d9f --- /dev/null +++ b/src/prj/mmv/manual-mv.sh.html @@ -0,0 +1,5 @@ +$ <span class="sh-fn">mv</span> bytecodeVm.c bytecode_vm.c +$ <span class="sh-fn">mv</span> fastLexer.c fast_lexer.c +$ <span class="sh-fn">mv</span> fastLexer.h fast_lexer.h +$ <span class="sh-fn">mv</span> slowParser.c slow_parser.c +$ <span class="sh-fn">mv</span> slowParser.h slow_parser.h diff --git a/src/prj/mmv/mmv-rev-zero.sh.html b/src/prj/mmv/mmv-rev-zero.sh.html new file mode 100644 index 0000000..4be992e --- /dev/null +++ b/src/prj/mmv/mmv-rev-zero.sh.html @@ -0,0 +1,2 @@ +$ <span class="sh-fn">ls</span> --zero foo$'\n'bar baz | <span class="sh-fn">mmv</span> -0 rev +mmv: Files have been added or removed during editing diff --git a/src/prj/mmv/mmv-rev.sh.html b/src/prj/mmv/mmv-rev.sh.html new file mode 100644 index 0000000..3d2e683 --- /dev/null +++ b/src/prj/mmv/mmv-rev.sh.html @@ -0,0 +1,2 @@ +$ <span class="sh-fn">ls</span> foo$'\n'bar baz | <span class="sh-fn">mmv</span> rev +mmv: No such file or directory (os error 2) diff --git a/src/prj/mmv/mmv-tr.sh.html b/src/prj/mmv/mmv-tr.sh.html new file mode 100644 index 0000000..4d8773a --- /dev/null +++ b/src/prj/mmv/mmv-tr.sh.html @@ -0,0 +1 @@ +$ <span class="sh-fn">ls</span> LICENSE README | <span class="sh-fn">mmv</span> tr A-Z a-z diff --git a/src/prj/mmv/mmv-verbose.sh.html b/src/prj/mmv/mmv-verbose.sh.html new file mode 100644 index 0000000..3767416 --- /dev/null +++ b/src/prj/mmv/mmv-verbose.sh.html @@ -0,0 +1,8 @@ +$ <span class="sh-fn">ls</span> foo bar | <span class="sh-fn">mmv</span> -v awk <span class="sh-str">'{ printf "%d-%s\n", NR, $0 }'</span> +… +created directory ‘/home/thomas/.cache/mmv/1692102229’ +created directory ‘/home/thomas/.cache/mmv/1692102229/home/thomas/code/repo/Mango0x45/mmv’ +copied ‘/home/thomas/code/repo/Mango0x45/mmv/bar’ -> ‘/home/thomas/.cache/mmv/1692102229/home/thomas/code/repo/Mango0x45/mmv/bar’ +created directory ‘/home/thomas/.cache/mmv/1692102229/home/thomas/code/repo/Mango0x45/mmv’ +copied ‘/home/thomas/code/repo/Mango0x45/mmv/foo’ -> ‘/home/thomas/.cache/mmv/1692102229/home/thomas/code/repo/Mango0x45/mmv/foo’ +… diff --git a/src/prj/mmv/reverse-embedded-newline.sh.html b/src/prj/mmv/reverse-embedded-newline.sh.html new file mode 100644 index 0000000..ff84d5c --- /dev/null +++ b/src/prj/mmv/reverse-embedded-newline.sh.html @@ -0,0 +1,3 @@ +$ <span class="sh-fn">ls</span> --zero foo$'\n'bar baz | <span class="sh-fn">mmv</span> -0 rev -0 +$ <span class="sh-fn">ls</span> +'rab'$'\n''oof' zab diff --git a/src/prj/mmv/sed-debugging-rev.sh.html b/src/prj/mmv/sed-debugging-rev.sh.html new file mode 100644 index 0000000..f1fddb1 --- /dev/null +++ b/src/prj/mmv/sed-debugging-rev.sh.html @@ -0,0 +1,3 @@ +$ <span class="sh-fn">ls</span> -U --zero foo$'\n'bar baz | <span class="sh-fn">rev</span> | <span class="sh-fn">sed</span> -n l +oof$ +\000zab\000rab$ diff --git a/src/prj/mmv/sed-debugging.sh.html b/src/prj/mmv/sed-debugging.sh.html new file mode 100644 index 0000000..e61cde4 --- /dev/null +++ b/src/prj/mmv/sed-debugging.sh.html @@ -0,0 +1,3 @@ +$ <span class="sh-fn">ls</span> -U --zero foo$'\n'bar baz | <span class="sh-fn">sed</span> -n l +foo$ +bar\000baz\000$ diff --git a/src/prj/mmv/sha1sum-long-example.sh.html b/src/prj/mmv/sha1sum-long-example.sh.html new file mode 100644 index 0000000..ddbda86 --- /dev/null +++ b/src/prj/mmv/sha1sum-long-example.sh.html @@ -0,0 +1,11 @@ +$ <span class="sh-fn">touch</span> foo bar +$ <span class="sh-fn">cat</span> <<<span class="sh-hd">EOF</span> >hash-filename +<span class="sh-hd">#!/bin/sh</span> + +<span class="sh-hd">sha1sum | awk '{ print \$1 }'</span> +<span class="sh-hd">EOF</span> +$ <span class="sh-fn">chmod</span> +x hash-filename +$ <span class="sh-fn">ls</span> foo bar | <span class="sh-fn">mmv</span> -i ./hash-filename +$ <span class="sh-fn">ls</span> +e242ed3bffccdf271b7fbaf34ed72d089537b42f hash-filename +f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 diff --git a/src/prj/mmv/sha1sum-short-example.sh.html b/src/prj/mmv/sha1sum-short-example.sh.html new file mode 100644 index 0000000..99e781c --- /dev/null +++ b/src/prj/mmv/sha1sum-short-example.sh.html @@ -0,0 +1,6 @@ +$ <span class="sh-fn">touch</span> foo bar +$ <span class="sh-fn">ls</span> | <span class="sh-fn">mmv</span> -i sha1sum +$ <span class="sh-fn">ls</span> | <span class="sh-fn">mmv</span> awk <span class="sh-str">'{ print $1 }'</span> +$ <span class="sh-fn">ls</span> +e242ed3bffccdf271b7fbaf34ed72d089537b42f +f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 diff --git a/src/prj/mmv/tr.sh.html b/src/prj/mmv/tr.sh.html new file mode 100644 index 0000000..db08c38 --- /dev/null +++ b/src/prj/mmv/tr.sh.html @@ -0,0 +1,3 @@ +$ <span class="sh-fn">ls</span> LICENSE README | <span class="sh-fn">tr</span> A-Z a-z +license +readme diff --git a/src/prj/mmv/vim.html b/src/prj/mmv/vim.html new file mode 100644 index 0000000..cd89cd8 --- /dev/null +++ b/src/prj/mmv/vim.html @@ -0,0 +1,2 @@ +foo\nbar +baz diff --git a/src/prj/mmv/vipe.sh.html b/src/prj/mmv/vipe.sh.html new file mode 100644 index 0000000..c56ff08 --- /dev/null +++ b/src/prj/mmv/vipe.sh.html @@ -0,0 +1 @@ +$ <span class="sh-fn">ls</span> --zero foo$'\n'bar baz | <span class="sh-fn">mmv</span> -0e vipe diff --git a/src/prj/mmv/with-i-flag.dot b/src/prj/mmv/with-i-flag.dot new file mode 100644 index 0000000..0757cd7 --- /dev/null +++ b/src/prj/mmv/with-i-flag.dot @@ -0,0 +1,20 @@ +digraph { + bgcolor = "#00000000" + + graph[rankdir=LR] + node[ + shape=rect, + fontname="Iosevka Smooth", + color="#C5C8C6", + fontcolor="#C5C8C6" + ] + edge[color="#C5C8C6"] + + n0[label="sha1sum"] + n1[label="sha1sum"] + ls[label="ls | mmv -i"] + + ls -> {foo, bar} + foo -> n0 -> "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 -" + bar -> n1 -> "e242ed3bffccdf271b7fbaf34ed72d089537b42f -" +} diff --git a/src/prj/mmv/without-i-flag.dot b/src/prj/mmv/without-i-flag.dot new file mode 100644 index 0000000..d68bb82 --- /dev/null +++ b/src/prj/mmv/without-i-flag.dot @@ -0,0 +1,16 @@ +digraph { + bgcolor = "#00000000" + + graph[rankdir=LR] + node[ + shape=rect, + fontname="Iosevka Smooth", + color="#C5C8C6", + fontcolor="#C5C8C6" + ] + edge[color="#C5C8C6"] + + ls[label="ls | mmv"] + + ls -> "foo\nbar" -> sha1sum -> "928f1abc474008fb586d62bcc3e043c5e3acb2aa -" +} diff --git a/src/style.css b/src/style.css index 30e895d..ea9dc56 100644 --- a/src/style.css +++ b/src/style.css @@ -7,6 +7,7 @@ --accent: #DE935F; --lesser: #969896; --green: #B5BD68; + --aqua: #8ABEB7; --blue: #81A2BE; --red: #C66; } @@ -26,7 +27,7 @@ body > * { grid-column: 2; } pre, code, kbd, samp { font-family: var(--mono); - font-size: 0.8rem; + font-size: 0.9rem; color: var(--accent); overflow-wrap: none; } @@ -37,7 +38,8 @@ h1 { font-size: 1.8rem; margin-bottom: 0; } -h2 { font-size: 1.2rem; } +h2 { font-size: 1.2rem; } +:is(h1, h2) > code { font-size: 1em; } a { color: var(--accent); } a:hover { text-decoration: none; } @@ -107,12 +109,14 @@ figure > pre > code::before { background-color: var(--bg); } -figure > img { +figure > :where(img, object) { display: block; margin: 16px auto; - width: 50%; } +figure > img { width: 50%; } +figure > object { width: 100%; } + header > div { display: flex; justify-content: space-between; @@ -125,10 +129,21 @@ header > div menu { } header > div li { list-style: none; } -.c-cmt { color: var(--lesser); } -.c-fn { color: var(--accent); } -.c-kw { color: var(--green); } -.c-pp { color: var(--blue); } +p { margin-top: 0; } + +dl { + display: grid; + grid-template-columns: max-content auto; +} + +.c-cmt, .sh-cmt { color: var(--lesser); font-style: italic; } + .sh-var { color: var(--blue); font-style: italic; } +.c-fn, .sh-fn { color: var(--accent); } +.c-kw, .sh-kw { color: var(--green); } +.c-pp { color: var(--blue); } + .sh-str { color: var(--aqua); } + .sh-hd { color: var(--aqua); } + .diff-ins { color: var(--green); } .diff-del { color: var(--red); } .diff-loc { color: var(--lesser); } @@ -136,10 +151,10 @@ header > div li { list-style: none; } @media (min-width: 40em) { body { - font-size: 1.2rem; + font-size: 1.3rem; line-height: 1.5; } - pre, code, kbd, samp { font-size: 1rem; } + pre, code, kbd, samp { font-size: 1.2rem; } h1 { font-size: 3rem; } h2 { font-size: 2.5rem; } @@ -178,6 +193,7 @@ header > div li { list-style: none; } abbr.cpu::before { content: 'Central Processing Unit'; } abbr.ec::before { content: 'Embedded Controller'; } + abbr.gnu::before { content: 'GNU’s Not UNIX'; } abbr.html::before { content: 'Hypertext Markup Language'; } abbr.led::before { content: 'Light-Emitting Diode'; } abbr.rgb::before { content: 'Red Green Blue'; } |