···11+---
22+title: Fun with Redirection
33+date: 2021-09-22
44+author: Twi
55+tags:
66+ - shell
77+ - redirection
88+ - osdev
99+---
1010+1111+When you're hacking in the shell or in a script, sometimes you want to change
1212+how the output of a command is routed. Today I'm gonna cover common shell
1313+redirection tips and tricks that I use every day at work and how it all works
1414+under the hood.
1515+1616+Let's say you're trying to capture the output of a command to a file, such as
1717+`uname -av`:
1818+1919+```console
2020+$ uname -av
2121+Linux shachi 5.13.15 #1-NixOS SMP Wed Sep 8 06:50:21 UTC 2021 x86_64 GNU/Linux
2222+```
2323+2424+You could copy that to the clipboard and paste it into a file, but there is a
2525+better way thanks to the `>` operator:
2626+2727+```console
2828+$ uname -av > uname.txt
2929+$ cat uname.txt
3030+Linux shachi 5.13.15 #1-NixOS SMP Wed Sep 8 06:50:21 UTC 2021 x86_64 GNU/Linux
3131+```
3232+3333+Let's say you want to run this on a few machines and put all of the output into
3434+`uname.txt`. You could write a shell script loop like this:
3535+3636+```sh
3737+# make sure the file doesn't already exist
3838+rm -f uname.txt
3939+4040+for host in shachi chrysalis kos-mos ontos pneuma
4141+do
4242+ ssh $host -- uname -av >> uname.txt
4343+done
4444+```
4545+4646+Then `uname.txt` should look like this:
4747+4848+```
4949+Linux shachi 5.13.15 #1-NixOS SMP Wed Sep 8 06:50:21 UTC 2021 x86_64 GNU/Linux
5050+Linux chrysalis 5.10.63 #1-NixOS SMP Wed Sep 8 06:49:02 UTC 2021 x86_64 GNU/Linux
5151+Linux kos-mos 5.10.45 #1-NixOS SMP Fri Jun 18 08:00:06 UTC 2021 x86_64 GNU/Linux
5252+Linux ontos 5.10.52 #1-NixOS SMP Tue Jul 20 14:05:59 UTC 2021 x86_64 GNU/Linux
5353+Linux pneuma 5.10.57 #1-NixOS SMP Sun Aug 8 07:05:24 UTC 2021 x86_64 GNU/Linux
5454+```
5555+5656+Now let's say you want to extract all of the hostnames from that `uname.txt`.
5757+The pattern of the file seems to specify that fields are separated by spaces and
5858+the hostname seems to be the second space-separated field in each line. You can
5959+use the `cut` command to select that small subset from each line, and you can
6060+feed the `cut` command's standard input using the `<` operator:
6161+6262+```console
6363+$ cut -d' ' -f2 < uname.txt
6464+shachi
6565+chrysalis
6666+kos-mos
6767+ontos
6868+pneuma
6969+```
7070+7171+[It's worth noting that a lot of these core CLI utilities are built on the idea
7272+that they are _filters_, or things that take one infinite stream of text in on
7373+one end and then return another stream of text out the other
7474+end. This is done through a channel called "standard input/output", where
7575+standard input refers to input to the command and standard output refers to the
7676+output of the command.](conversation://Mara/hacker)
7777+7878+[That's a great metaphor, let's build onto it using the `|` (pipe)
7979+operator. The pipe operator lets you pipe the standard output of one command to
8080+the standard input of another.](conversation://Cadey/enby)
8181+8282+[You mentioned that you can pass files as input and output for commands, does
8383+this mean that standard input and standard output are
8484+files?](conversation://Mara/happy)
8585+8686+[Precisely! They are just files that are automatically open for every process.
8787+Usually commands will output to standard out and some will also accept input via
8888+standard in.](conversation://Cadey/enby)
8989+9090+[Doesn't that have some level of overhead though? Isn't it expensive to spin up
9191+a whole heckin' `cat` process for that?](conversation://Mara/hmm)
9292+9393+[Not on any decent system made in the last 20 years. This may have some impact
9494+on Windows (because they have core architectural mistakes that make processes
9595+take up to 100 milliseconds to spin up), but this is about Unix/Linux. I think
9696+these should work on Windows too if you use Cygwin, but if you're using WSL you
9797+shouldn't have any real issues there](conversation://Cadey/coffee)
9898+9999+Let's say we want to rewrite that `cut` command above to use pipes. You could
100100+write it like this:
101101+102102+```sh
103103+cat uname.txt | cut -d' ' -f2
104104+```
105105+106106+[The mnemonic we use for remembering the `cut` command is that fields are
107107+separated by the `d`elimiter and you cut out the nth
108108+`f`ield/s. You can use ](conversation://Mara/hacker)
109109+110110+This will get you the exact same output:
111111+112112+```console
113113+$ cat uname.txt | cut -d' ' -f2
114114+shachi
115115+chrysalis
116116+kos-mos
117117+ontos
118118+pneuma
119119+```
120120+121121+Personally I prefer writing shell pipelines like that as it makes it a bit
122122+easier to tack on more specific selectors or operations as you go along. For
123123+example, if you wanted to sort them you could pipe the result to `sort`:
124124+125125+```console
126126+$ cat uname.txt | cut -d' ' -f2 | sort
127127+chrysalis
128128+kos-mos
129129+ontos
130130+pneuma
131131+shachi
132132+```
133133+134134+This lets you gradually build up a shell pipeline as you drill down to the data
135135+you want in the format you want.
136136+137137+[I wanted to save this compiler error to a file but it didn't work. I tried
138138+doing this:](conversation://Mara/hmm)
139139+140140+```console
141141+$ rustc foo.rs > foo.log
142142+```
143143+144144+But the output printed to the screen instead of the file:
145145+146146+```console
147147+$ rustc foo.rs > foo.log
148148+error: expected one of `!` or `::`, found `main`
149149+ --> foo.rs:1:5
150150+ |
151151+1 | fun main() {}
152152+ | ^^^^ expected one of `!` or `::`
153153+154154+error: aborting due to previous error
155155+156156+$ cat foo.log
157157+$
158158+```
159159+160160+This happens because there are actually _two_ output streams per program. There
161161+is the standard out stream and there is also a standard error stream. The reason
162162+that standard error exists is so that you can see if any errors have happened if
163163+you redirect standard out.
164164+165165+Sometimes standard out may not be a stream of text, say you have a compressed
166166+file you want to analyze and there's an issue with the decompression. If the
167167+decompressor wrote its errors to the standard output stream, it could confuse or
168168+corrupt your analysis.
169169+170170+However, we can redirect standard error in particular by modifying how we
171171+redirect to the file:
172172+173173+```console
174174+$ rustc foo.rs 2> foo.log
175175+$ cat foo.log
176176+error: expected one of `!` or `::`, found `main`
177177+ --> foo.rs:1:5
178178+ |
179179+1 | fun main() {}
180180+ | ^^^^ expected one of `!` or `::`
181181+182182+error: aborting due to previous error
183183+```
184184+185185+[Where did the `2` come from?](conversation://Mara/wat)
186186+187187+So I mentioned earlier that redirection modifies the standard input and output
188188+of programs. This is not entirely true, but it was a convenient half-truth to
189189+help build this part of the explanation.
190190+191191+For every process on a Unix-like system (such as Linux and macOS), the kernel
192192+stores a list of active file-like objects. This includes real files on the
193193+filesystem, pipes between processes, network sockets, and more. When a program
194194+reads or writes a file, they tell the kernel which file they want to use by
195195+giving it a number index into that list, starting at zero. Standard in/out/error
196196+are just the conventional names for the first three open files in the list, like
197197+this:
198198+199199+| File Descriptor | Purpose |
200200+| :------ | :------- |
201201+| 0 | Standard input |
202202+| 1 | Standard output |
203203+| 2 | Standard error |
204204+205205+Shell redirection simply changes which files are in that list of open files when
206206+the program starts running.
207207+208208+That is why you use a `2` there, because you are telling the shell to change
209209+file descriptor number `2` of the `rustc` process to point to the filesystem
210210+file `foo.log`, which in turn makes the standard error of `rustc` get written to
211211+that file for you.
212212+213213+In turn, this also means that `cat foo.txt > foo2.txt` is actually a shortcut
214214+for saying `cat foo.txt 1> foo2.txt`, but the `1` can be omitted there because
215215+standard out is usually the "default" output that most of these kind of
216216+pipelines cares about.
217217+218218+[How would I get both standard output and standard error in the same
219219+file?](conversation://Mara/hmm)
220220+221221+The cool part about the `>` operator is that it doesn't just stop with output to
222222+files on the desk, you can actually have one file descriptor get pointed to
223223+another. Let's say you have a need for both standard out and standard error to
224224+go to the same file. You can do this with a command like this:
225225+226226+```
227227+$ rustc foo.rs 2>&1 > foo.log
228228+```
229229+230230+This tells the shell to point standard error to standard out and then the
231231+combined output to `foo.log`. There's a short form of this too:
232232+233233+```
234234+$ rustc foo.rs &> foo.log
235235+```
236236+237237+[Where can I expect to use that?](conversation://Mara/hmm)
238238+239239+[It's a bourne shell extension, but I've tested it in `zsh` and `fish`. You can
240240+also do `&|` to pipe both standard out and standard error at the same time in
241241+the same way you'd do `2>&1 | whatever`.](conversation://Cadey/enby)
242242+243243+That will put standard out and standard error to `foo.log` the same way that
244244+`2>&1 > foo.log` will. You can also use this with `>>`:
245245+246246+```
247247+$ rustc foo.rs &>> foo.log
248248+$ cat foo.log
249249+error: expected one of `!` or `::`, found `main`
250250+ --> foo.rs:1:5
251251+ |
252252+1 | fun main() {}
253253+ | ^^^^ expected one of `!` or `::
254254+255255+error: aborting due to previous error
256256+257257+error: expected one of `!` or `::`, found `main`
258258+ --> foo.rs:1:5
259259+ |
260260+1 | fun main() {}
261261+ | ^^^^ expected one of `!` or `::`
262262+263263+error: aborting due to previous error
264264+```
265265+266266+[How do I redirect standard in to a file?](conversation://Mara/hmm)
267267+268268+The answer there is not directly! There is a workaround in the form of a tool
269269+called `tee` which outputs its standard in to both standard out and a file. For
270270+example:
271271+272272+```console
273273+$ dmesg | tee dmesg.txt | grep 'msedge'
274274+[ 70.585463] traps: msedge[4715] trap invalid opcode ip:5630ddcedc4c sp:7ffd41f67700 error:0 in msedge[5630d8fc2000+952d000]
275275+[ 70.702544] traps: msedge[4745] trap invalid opcode ip:5630ddcedc4c sp:7ffd41f67700 error:0 in msedge[5630d8fc2000+952d000]
276276+[ 70.806296] traps: msedge[4781] trap invalid opcode ip:5630ddcedc4c sp:7ffd41f67700 error:0 in msedge[5630d8fc2000+952d000]
277277+[ 70.918095] traps: msedge[4889] trap invalid opcode ip:5630ddcedc4c sp:7ffd41f67700 error:0 in msedge[5630d8fc2000+952d000]
278278+[ 71.031938] traps: msedge[4926] trap invalid opcode ip:5630ddcedc4c sp:7ffd41f67700 error:0 in msedge[5630d8fc2000+952d000]
279279+[ 71.138974] traps: msedge[4935] trap invalid opcode ip:5630ddcedc4c sp:7ffd41f67700 error:0 in msedge[5630d8fc2000+952d000]
280280+[ 1169.163603] traps: msedge[35719] trap invalid opcode ip:556a93951c4c sp:7ffc533f35c0 error:0 in msedge[556a8ec26000+952d000]
281281+[ 1213.301722] traps: msedge[36054] trap invalid opcode ip:55a245960c4c sp:7ffe6d169b40 error:0 in msedge[55a240c35000+952d000]
282282+[10963.234459] traps: msedge[104732] trap invalid opcode ip:55fdb864fc4c sp:7ffc996dfee0 error:0 in msedge[55fdb3924000+952d000]
283283+```
284284+285285+This would put the output of the `dmesg` command (read from kernel logs) into
286286+`dmesg.txt`, as well as sending it into the grep command. You might want to do
287287+this when debugging long command pipelines to see exactly what is going into a
288288+program that isn't doing what you expect.
289289+290290+Redirections also work in scripts too. You can also set "default" redirects for
291291+every command in a script using the `exec` command:
292292+293293+```sh
294294+exec > out.log 2> error.log
295295+296296+ls
297297+rustc foo.rs
298298+```
299299+300300+This will have the file listing from `ls` written to `out.log` and any errors
301301+from `rustc` written to `error.log`.
302302+303303+A lot of other shell tricks and fun is built on top of these fundamentals. For
304304+example you can take a folder, zip it up and then unzip it over on another
305305+machine using a command like this:
306306+307307+```
308308+$ tar cz ./blog | ssh pneuma tar xz -C ~/code/christine.website/blog
309309+```
310310+311311+This will run `tar` to create a compressed copy of the `./blog` folder and then
312312+pipe that to tar on another computer to extract that into
313313+`~/code/christine.website/blog`. It's just pipes and redirection all the way
314314+down! Deep inside `ssh` it's really just piping output of commands back and
315315+forth over an encrypted network socket. Connecting to an IRC server is just
316316+piping in and out data to the chat server, even more so if you use TLS to
317317+connect there. In a way you can model just about everything in Unix with pipes
318318+and file descriptors because that is the cornerstone of its design: Everything
319319+is a file.
320320+321321+[This doesn't mean it's literally a file on the disk, it means you can _interact
322322+with_ just about everything using the same system interface as you do with
323323+files. Even things like hard disks and video cards.](conversation://Mara/hacker)
324324+325325+Here's a fun thing to do. Using [`curl`](https://curl.se/) to read the contents
326326+of a URL and [`jq`](https://stedolan.github.io/jq/) to select out bits from a
327327+JSON stream, you can make a script that lets you read the most recent title from
328328+my blog's [JSONFeed](/blog.json):
329329+330330+```sh
331331+#!/usr/bin/env bash
332332+# xeblog-post.sh
333333+334334+curl -s https://christine.website/blog.json | jq -r '.items[0] | "\(.title) \(.url)"'
335335+```
336336+337337+At the time of writing this post, here is the output I get from this command:
338338+339339+```
340340+$ ./xeblog-post.sh
341341+Anbernic RG280M Review https://christine.website/blog/rg280m-review
342342+```
343343+344344+What else could you do with pipes and redirection? The cloud's the limit!
345345+346346+---
347347+348348+Thanks to violet spark for looking over this post and fact-checking as well as
349349+helping mend some of the brain dump and awkward wording into more polished
350350+sentences.
+8
config.dhall
···7070 , twitter = Some "BeJustFine"
7171 , inSystem = True
7272 }
7373+ , Author::{
7474+ , name = "Nicole"
7575+ , handle = "Twi"
7676+ , picUrl = None Text
7777+ , link = None Text
7878+ , twitter = None Text
7979+ , inSystem = True
8080+ }
7381 ]
7482 , port = defaultPort
7583 , clackSet = [ "Ashlynn" ]