Unix: add tee command notes

This commit is contained in:
Akemi Izuko 2023-12-29 17:49:02 -07:00
parent b5a0b5ec4f
commit 0e60a4fd8f
Signed by: akemi
GPG key ID: 8DE0764E1809E9FC

117
src/content/unix/tee.md Normal file
View file

@ -0,0 +1,117 @@
---
title: 'Tee Command'
description: 'The powerful shell-stream controller'
updateDate: 'December 29 2023'
---
# Unix Tee
The `tee` command is available on all Unix systems. It takes in any number of
files as arguments and copies the `stdin` to all these files and the `stdout`.
This can be a very helpful command when using Unix pipes.
## Multi-file Output
It can be helpful to duplicate the output of a command across multiple files. By
default, shells only provide one stdout stream, so it'll take several commands
or a for-loop to write to several files.
`tee` provides an alternative. The command below copies the output across 3
files:
```bash
echo "something" | tee file1 | tee file2 > file3
```
`tee` can also take in multiple file name arguments, where the output is copied
to all of them. The following command is equivalent to the above:
```bash
echo "something" | tee file1 file2 file3 > /dev/null
```
This can further be further exploited with bash for loops. The following will
copy the message across 100 different files:
```bash
echo "something" | tee $(for x in {1..100}; do echo file$x; done) >/dev/null
```
You can similarly append to files using the `-a` option. This is analogous to
the `bash` syntax `>>`.
The following command will append to files 1 and 3, but overwrite file 2:
```bash
echo "something" | tee -a file1 | tee file2 >> file3
```
### Writing Stderr
Consider this C program, which will write "something" to the `stderr`:
```c
#include <stdio.h>
int main(void) {
fprintf(stderr, "something\n");
}
```
`tee` only reads from the `stdin`. To use this output with `tee`, we'll need to
first redirect the `stderr` to `stdout`. Suppose the C program above is compiled
into an executable called `a.out`:
```bash
./a.out 2>&1 | tee file1 file2
```
## Elevating Permissions
It's common on a single-user system, like your personal computer, to need to
write to files that are write-only for the root user. For example consider the
following command:
```bash
sudo echo "something" > root_file
```
This won't work, as the `sudo` only applies to `echo "something"` not `>
root_file`. One alternative would be to run the entire shell as root:
```bash
sudo bash -c 'echo "something" > root_file'
```
But now you've given root access to the entire command! We only want to give
root access to the "write" operation. `tee` can do just that!
Both of the following will use the root user to write to the file `root_file`,
but the `echo` command itself will still be run by your user.
```bash
echo "something" | sudo tee root_file
echo "something" | sudo tee root_file &>/dev/null
```
`echo` isn't a very serious application of `tee`, though there are many cases
where we only want to elevate permissions for the write and not the whole
command.
### Elevating Permissions for Vim
If you open a file that's inaccessible for writing by your user with Vim, `tee`
can be used to switch to the root user just for writing!
The following vim command writes your current buffer to the file as the root
user:
```vim
:w ! sudo tee %
```
To deconstruct that command a bit, we start with `:w` which is the write
command. We use `!` to invoke a shell. `:w` will provide the current buffer as
through the stdin to the shell. We run the command `sudo tee %`, where Vim
expands `%` to the current file name. `tee` will overwrite the file name expand
from `%` with the stdin provided by the `:w` command.