How to use the VHS command to turn a Linux terminal session into a beautiful compact GIF

Have you ever seen those GIFs that animate the command line and wondered why they look so good? Or have you seen a small GIF and wondered why they are so small? It’s not as difficult as you might think, and I tackle both with two easy-to-use commands.
VHS is a command for generating GIFs from a script file and Gifsicle is a command for editing them. When used together, they can create very compact and professional-looking GIFs. I explain what VHS is, how to use it and how to compress the resulting GIF so that it is barely larger than a regular PNG file.
A VHS demo
They say a picture is worth a thousand words, so here’s one that’s literally true in this case:
This animation was for an article I wrote on fzf. Here is another image from the same article:
They’re beautiful, aren’t they? Believe it or not, it wasn’t difficult or overly technical, nor did it result in a big, bulky image. In fact, the first image is only 223 KB and the second is even smaller at 22 KB. So how did I do it? With VHS, Gifsicle and a little witchcraft.
Key concepts of VHS explained
The basic concept behind VHS is the tape file, a sequence of scripted commands that simulates a terminal session. Here is a simple example:
Output output.gif
Type 'ls -al' Enter
Sleep 2s
This particular band does three things:
-
Specifies an “Output” GIF file.
-
Simulates entering a command (“Type”) then pressing “Enter”.
-
“Sleep” for 2 seconds to allow the player to process actions.
When you run this tape, you will see the command entered into the terminal and executed. The animation will then loop after a brief pause.
Generate the GIF file by running vhs my-file.tape in your terminal.
The GIF obtained:
Now let’s write a tape
Now let’s write a more complex example and focus on some details.
Understanding the tape file
The following tape file will create the “needle in a haystack” GIF seen previously. I will clarify the meaning of each line below.
Output unoptimized.gif
Require fzf
Set FontSize 32
Set Width 1200
Set Height 675
Set TypingSpeed 0.15
Set Shell "zsh"
Set Framerate 5
Hide
Type 'eval "$(fzf --zsh)"' Enter
Type 'clear' Enter
Show
Type 'cat lorem.txt'
Sleep 1
Enter
Sleep 2s
Type 'cat lorem.txt | fzf'
Sleep 1
Enter
Sleep 0.5
Type "needle"
Sleep 1
Enter
Sleep 3s
The “Require” directive ensures that “fzf” is present on your system before continuing.
The “Output” directive requires no explanation, but be aware that you can render to GIF, MP4, WebM, or to a directory of individual images to be processed separately.
“Set” directives must be specified next, at the top of the tape file. They apply various options, such as “Width”, “Height”, “Shell”, “Framrate” and even “TypingSpeed”. These options are self-explanatory. You can change the typing speed anywhere in the tape file, but you must specify all other options at the top.
I deliberately set the frame rate low to reduce the image size, but this can sometimes cause rendering issues. If so, increase the “Framrate” or remove the directive altogether.
The “Hide” section is next, and this is where we do the script setup. We add here all the commands that we do not want to render. In this example, we configure the fzf environment and clear the terminal to remove all spam messages from its shell evaluation. We end the “Hide” section with a “Show” directive, which tells VHS to record the following.
Line 17 is where the visual rendering begins. First, it contains a file full of Lorem Ipsum text – this is the haystack we’re going to run through with fzf. The script often “Sleeps” because it improves the appearance of the animation.
On line 22, we grab the text file, redirect it to fzf, hit the Enter key, and on line 22, we finally type the search term: needle. We end the script with a long “Sleep” to give the user time to process the image.
The entire command should create a text file to display its contents, then run it again and search for it using fzf.
Create the tape file
Before generating a GIF from your tape file, make sure you have fzf installed. You also need to create a lorem ipsum text file. So run the following command:
{
echo -e "You can use fzf to find a needle in a haystack.\n";
curl 'https://pastebin.com/raw/nRc62E07';
} > lorem.txt
Now create a tape file called fzf-demo.tape, enter the contents of the tape file and run the following command to generate the GIF:
vhs fzf-demo.tape
This command will produce a GIF called “unoptimized.gif”, which we will compress in the next section.
Compress GIF
The “unoptimized.gif” we created is around 1.2 MB, which is often too large. We can compress it with a command called Gifsicle. You can install it with the following commands:
For Fedora, run the following command:
sudo dnf install gifsicle
For Debian, run the following command:
sudo apt install gifsicle
For Arch Linux, run the following command:
sudo pacman -S gifsicle
Now that Gifsicle is installed, you can compress the “unoptimized.gif” file with the following command:
gifsicle -O3 --colors 8 unoptimized.gif -o optimized.gif
This will compress the GIF to approximately 228 KB.
Instead of using a full color palette, I opted for eight bits. Besides reducing the resolution, reducing the number of colors is the biggest space saver. If your terminal session contains a wide range of colors that you want to keep, set it higher, up to 256, but be prepared for a much larger file.
Another way to reduce image size is to reduce the frame rate. I set it to 5, which seems to be a good balance between compactness and readability. Since the frame rate is so low, the output GIF will contain duplicate frames, but we can remove them automatically. When you use the “-O3” (or -O2) flag with Gifsicle, it will find duplicate images, remove extras, and add an appropriate delay to the remaining image.
If you compare these two images before and after, you can see that one image contains much fewer images and that the delay between each image has become variable: this is the optimized image.
For action-packed GIFs (e.g., rapidly changing text), removing duplicate images has a significant impact on file size. For smaller, simpler animations, the space saved is proportionately smaller but still considerable.
I didn’t mention it earlier, but VHS has a record command that will automatically create a tape from your terminal actions. You are supposed to edit the tape afterwards. Execute vhs recording > my.tapeperform your actions, then type exit in your shell when you’re done. It looks like you will exit your shell, but that is not the case; VHS will create a tape file for you.
I recommend reading the official documentation yourself (found on its GitHub page). Understand that VHS is not perfect and there may be bugs. However, it works most of the time, and it’s a lot less work than screen recording. You don’t think so? Try the “record” function, edit the tape file and run the two necessary commands.



