Score:7

How to replace cat with bat system-wide Ubuntu 22.04

pm flag

I am using Ubuntu 22.04 with bash, pretty vanilla, and have added alias cat="batcat" to ~/.bashrc. The program batcat or bat being referred to can be found here: https://github.com/sharkdp/bat

So, when I run cat ~/.bashrc, for instance, it outputs the contents of the file using batcat. However, when I run a command such as crontab -l, the output appears to be using cat instead of batcat.

Any help would be highly appreciated!

It appears that crontab -l is not using cat, due to the output of strace crontab -l |& grep cat. Does that mean there's nothing I can do?

It is still possible to grab the output and feed it into crontab, using crontab -l | batcat or crontab -l | cat.

Artur Meinild avatar
vn flag
Nice utility - thanks for bringing this to my attention!
marcelm avatar
cn flag
**IMPORTANT**: You ask about "replacing cat with bat system-wide", and some of the answers provide means to do so. However, `cat` is not just used interactively by users; it is also used in system scripts as `cat <file> | filter | ...` or `DATA="$(cat <file>)"`. If `bat` doesn't behave _exactly_ the same in these situations, your system may end up breaking in unexpected ways. I very much recommend NOT replacing cat this way, but simply learn to use `bat` instead of `cat` in interactive shell sessions, and set `bat` as a pager. That way you won't get unexpected side effects.
cn flag
@marcelm, it does: "Whenever the output of bat goes to a non-interactive terminal, i.e. when the output is piped into another process or into a file, bat will act as a drop-in replacement for cat(1) and fall back to printing the plain file contents."
marcelm avatar
cn flag
@glennjackman It promises to do so; whether it actually does remains to be seen ;) Some things that could go wrong: it doesn't preserve a missing final newline; it doesn't preserve (mixed) newline types (`\n` vs `\r\n`); it mangles binary data; it errors out because some bat-specific configuration doesn't jive well with the current context within it is run (early boot where not everything is available; cron with missing env vars; commands run as different users). It's asking for trouble is what I'm saying.
marcelm avatar
cn flag
@glennjackman I found an example; `bat /dev/zero | dd bs=64 count=1 of=/dev/null` exhausts all system memory and crashes, where `cat` works just fine.
MJ713 avatar
cn flag
I suspect this is an [XY problem](https://meta.stackexchange.com/a/66378/620936).
Steve Summit avatar
us flag
[obGeezerRant] My goodness. Rob Pike once wrote a paper *cat -v Considered Harmful* excoriating the `-v` option in plain `cat` (a paper so influential it spawned [a whole website](http://harmful.cat-v.org/cat-v/)). This `bat` would have poor Rob spinning in his grave (that is, if he weren't still alive).
Score:31
cn flag

How to replace cat with bat system-wide Ubuntu 22.04

I would recommend strongly against that.

Cat is not just for interactive use on the shell; it is also used in system scripts as cat <file> | filter | ... , or DATA="$(cat <file>)". If Bat doesn't behave exactly the same in these situations, your system may end up breaking in unexpected ways, and you may not even think to connect these breakages to your replacement of Cat.

(an example I found is bat /dev/zero | head -c1 that exhausts all system memory and then crashes, where cat works just fine; see issue #636)

I am using Ubuntu 22.04 with bash, pretty vanilla, and have added alias cat="batcat" to ~/.bashrc.

That's less harmful; it only affects your interactive use of the shell; not scripts, and not other (system) users.

Still, I would also recommend against that. Again, if Bat does not behave exactly as Cat, it may break still break your shell somewhat, for example if you copy/paste a command line that uses cat from somewhere.

For an example, see bat issue #2380, where someone complains that zsh-async breaks after doing alias cat=bat.

However, when I run a command such as crontab -l, the output appears to be using cat instead of batcat.

Just because something produces output, doesn't mean it uses Cat. crontab -l just prints its output. It's not fed through Cat, or even the system pager (see below). You'll have to use | batcat manually.

(that said, with the Bat version I have on my system, crontab -l | batcat does not colorize anything, so I'm not sure there's much value to be had here)

So, what can you do?

Well, first of all, you can simply use batcat <file> or <command> | batcat when you want the colorized output. This makes the usage more explicit, so it won't mess up anything it's not supposed to.

You're free to define another alias using alias XXX="batcat", so you can use it with less typing in interactive shell sessions. I recommend against aliasing over Cat, but bat or bc seem fine aliases to me.

In shells such as Zsh, you could consider a global alias to save even more typing and reduce the command to something like crontab -l B. But be careful, global aliases are their own can of worms.

Another idea is creating an alias that reruns the previous command, and pipes its output through Bat; for example: alias rebat='$(history -p !!) | batcat'.

Then, if you run crontab -l and decide you would've rather piped its output through Bat, you can simply type rebat, and that will run crontab -l | batcat for you.

Pagers

Unix has the concept of a pager; a program that can display text a page at a time and allows you to navigate through the text. The most ubiquitous pager is probably Less. Some programs (man, git, systemctl, etc) automatically send their output through a pager.

You could set Bat as the default pager using export PAGER=bat (e.g. in your .bashrc), that would make those programs use Bat by default.

Note that the pager MUST be called bat, not batcat. Bat only colorizes output, it does not offer pagination. If Bat sees that its output is too long for the terminal, it will automatically feed its output through $PAGER.

However, since $PAGER=bat, it will start a new Bat, and feed its output there. That new Bat sees the output is too long, so it will start a pager, which is yet another Bat. This repeats indefinitely (see issue 2023).

To prevent this, Bat detects if the pager is set to Bat and quietly uses Less instead for pagination. But this only works if the pager is set to bat, not batcat.

To achieve that, you could symlink batcat to bat somewhere. $HOME/bin would be my preference, but you need to have your shell set up to use that. /usr/local/bin would be a system-wide option.

in flag
Note that `bc` is actually a [command](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/bc.html), so it’s not exactly guaranteed to be a safe choice for an alias.
WhiteBerry avatar
pm flag
Thank you so much for this elaborate answer, I would have never imagined I would receive such an excellent answer! I will avoid the pager since the desired outcome is mostly covered by the more simple case
marcelm avatar
cn flag
@AustinHemmelgarn True, but `bc` is not installed by default. And since a shell alias should only affect interactive shell sessions, a `bc` alias is only really an issue for people who installed and actively use `bc`.
NotTheDr01ds avatar
vn flag
Any system script that requires compatibility with `cat` *should* be calling `/usr/bin/cat` explicitly - If it's not, then that's a bug that needs to be fixed on the script end. I agree that "copy/paste" scripts are far more likely to just call `cat` and potentially run into issues. I personally prefer to just map `batcat` to `bat` through `update-alternatives`.
marcelm avatar
cn flag
@NotTheDr01ds I'm sorry, but that's just crazy. Nobody writes scripts as `/usr/bin/cat <file> | /usr/bin/grep <pattern> | /usr/bin/sed <whatever>`. And doing so would make scripts _less_ portable, as the location of these tools vary. `cat`, for example _usually_ lives in `/bin/`, not `/usr/bin/`. Figuring out the correct location is the system's job, and that's what `$PATH` is for. Providing two `cat` variations with different compatibilities in different paths is the problem; not the script using unqualified `cat`.
NotTheDr01ds avatar
vn flag
@marcelm Agreed - Apologies. I *have* seen scripts that use the absolute-path to binaries when they need to ensure compatibility, and I really do consider this "best practice." I also thought it was more common than it apparently is. Unfortunately, plenty of *system* scripts in Ubuntu still use the first binary found on the path. Portability should not be the first priority for *system* scripts, but even so, Ubuntu symlinks `/bin` to `/usr/bin` anyway (and I believe most all distributions do nowadays).
Score:4
vn flag

There are two things going on here:

  • Having the output of crontab -e automatically page.
  • Replacing the system cat with batcat.

crontab -l paging

From man crontab:

The -l option causes the current crontab to be displayed on standard output.

So crontab -l is always going to go to standard output. There is no option that I can see for configuring a pager for the command.

It would be possible to write your own wrapper crontab function that detected -l and automatically appended a pager, but that's probably more trouble than it's worth. Probably best just to manually pipe to cat (or bat, or batcat) as needed after configuring the alternatives below.


Replacing system cat

Again, this isn't going to solve your crontab -l desires (since crontab doesn't use a pager in the first place), but you can certainly replace (or add) a cat alternative, although I agree with @marcelm that this isn't the best idea.

Ubuntu includes the Debian Alternatives system with the update-alternatives command which is perfect for creating the necessary symbolic links in a "standard" way.

sudo update-alternatives --install /usr/local/bin/cat cat /usr/bin/batcat 20
sudo update-alternatives --install /usr/local/bin/cat cat /usr/bin/cat 10

This does several things:

  • Sets up /usr/local/bin/cat and /etc/alternatives/cat symlinks for a new cat alternative.
  • Installs both /usr/bin/batcat and /usr/bin/cat as potential cat alternatives, with /usr/bin/batcat at the higher priority.
  • /usr/local/bin/cat will be always be symlinked to /etc/alternatives/cat (unless the alternative is removed).
  • As a result of the priorities, /etc/alternatives/cat will be symlinked to the currently selected alternative, which is /usr/bin/batcat.

If you need to temporarily revert the alternative, you can do so easily with:

sudo update-alternatives --config cat

This will give you the option to select any of the available cat alternatives.

Of course, a good application or script that wants to ensure the use of the "real" cat for compatibility will actually call /usr/bin/cat explicitly. In this case, the alternative isn't going to override the actual binary, nor should it.

Side-notes:

I'd recommend going ahead and creating the symlink for bat itself as well, just to "rename" it from batcat:

sudo update-alternatives --install /usr/local/bin/bat bat /usr/bin/batcat 20

I do the same thing with fdfind (by the same author as bat):

sudo update-alternatives --install /usr/local/bin/fd fd /usr/bin/fdfind 20
Score:3
cn flag

You can have the external cat command overridden by placing a symbolic link to the alternative command, i.e. batcat in a directory that comes earlier in the search PATH for the executables. That could be ~/.local/bin if the change is to affect only your user, or /usr/local/bin if the change is to affect all users.

Assuming that both cat and batcat are installed in /usr/bin, following command would create a symbolic link cat thet would point to batcat:

ln -s /usr/bin/batcat ~/.local/bin/cat

or for systemwide deployment:

sudo ln -s /usr/bin/batcat /usr/local/bin/cat

For this to work in all circumstances, the command line syntax of batcat should support these of cat. This likely will be the case here, because the tool may be designed as a substitute.

cn flag
`update-alternatives` would be appropriate here.
Score:1
pm flag

It is still possible to grab the output of crontab -l and feed it into bat/batcat using crontab -l | batcat or crontab -l | cat or crontab -l | bat

I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.