Unix: add tee command notes
This commit is contained in:
parent
b5a0b5ec4f
commit
0e60a4fd8f
117
src/content/unix/tee.md
Normal file
117
src/content/unix/tee.md
Normal 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.
|
Loading…
Reference in a new issue