7

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.

7
  • 1
    Nice utility - thanks for bringing this to my attention! Dec 6, 2022 at 10:34
  • 30
    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.
    – marcelm
    Dec 6, 2022 at 15:20
  • 7
    @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
    Dec 6, 2022 at 16:06
  • 7
    @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.
    – marcelm
    Dec 6, 2022 at 16:32
  • 3
    [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). This bat would have poor Rob spinning in his grave (that is, if he weren't still alive). Dec 6, 2022 at 23:00

4 Answers 4

31

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.

6
  • 7
    Note that bc is actually a command, so it’s not exactly guaranteed to be a safe choice for an alias. Dec 7, 2022 at 0:59
  • 3
    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
    – WhiteBerry
    Dec 7, 2022 at 3:36
  • @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.
    – marcelm
    Dec 7, 2022 at 8:59
  • 1
    @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.
    – marcelm
    Dec 8, 2022 at 12:31
  • 1
    @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). Dec 8, 2022 at 14:18
4

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
3

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.

1
  • 1
    update-alternatives would be appropriate here. Dec 6, 2022 at 16:02
1

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

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.