From dfe84da074ac14d2e0e036fdfe7609e0f5bfe687 Mon Sep 17 00:00:00 2001 From: artemis Date: Thu, 23 Sep 2021 04:17:04 -0700 Subject: [PATCH] clarify some language; insert spaces in args to confuse beginners less (#400) * clarify some language; insert spaces in args to confuse beginners less * fix 2>&1 footgun, explain it and how to avoid it * add cadence and AstroSnail to credits --- blog/fun-with-redirection-2021-09-22.markdown | 70 ++++++++++++++----- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/blog/fun-with-redirection-2021-09-22.markdown b/blog/fun-with-redirection-2021-09-22.markdown index df7b065..0757e8e 100644 --- a/blog/fun-with-redirection-2021-09-22.markdown +++ b/blog/fun-with-redirection-2021-09-22.markdown @@ -60,7 +60,7 @@ use the `cut` command to select that small subset from each line, and you can feed the `cut` command's standard input using the `<` operator: ```console -$ cut -d' ' -f2 < uname.txt +$ cut -d ' ' -f 2 < uname.txt shachi chrysalis kos-mos @@ -100,7 +100,7 @@ Let's say we want to rewrite that `cut` command above to use pipes. You could write it like this: ```sh -cat uname.txt | cut -d' ' -f2 +cat uname.txt | cut -d ' ' -f 2 ``` [The mnemonic we use for remembering the `cut` command is that fields are @@ -110,7 +110,7 @@ separated by the `d`elimiter and you cut out the nth This will get you the exact same output: ```console -$ cat uname.txt | cut -d' ' -f2 +$ cat uname.txt | cut -d ' ' -f 2 shachi chrysalis kos-mos @@ -123,7 +123,7 @@ easier to tack on more specific selectors or operations as you go along. For example, if you wanted to sort them you could pipe the result to `sort`: ```console -$ cat uname.txt | cut -d' ' -f2 | sort +$ cat uname.txt | cut -d ' ' -f 2 | sort chrysalis kos-mos ontos @@ -224,24 +224,52 @@ another. Let's say you have a need for both standard out and standard error to go to the same file. You can do this with a command like this: ``` -$ rustc foo.rs 2>&1 > foo.log +$ rustc foo.rs > foo.log 2>&1 ``` -This tells the shell to point standard error to standard out and then the -combined output to `foo.log`. There's a short form of this too: +This tells the shell to point standard out to `foo.log`, and then standard +error to standard out (which is now `foo.log`). There's a footgun here though; +the order of the redirects matters. Consider the following: + +``` +$ rustc foo.rs 2>&1 > foo.log +error: expected one of `!` or `::`, found `main` + --> foo.rs:1:5 + | +1 | fun main() {} + | ^^^^ expected one of `!` or `::` + +error: aborting due to previous error +$ cat foo.log +$ # foo.log is empty, why??? +``` + +We wanted to redirect stderr to `foo.log`, but that didn't happen. Why? Well, +the shell considers our redirects one at a time from left to right. When the +shell sees `2>&1`, it hasn't considered `> foo.log` yet, so standard out (`1`) +is still our terminal. It dutifully redirects stderr to the terminal, which is +where it was already going anyway. Then it sees `1 > foo.log`, so it redirects +standard out to `foo.log`. That's the end of it though. It doesn't +retroactively redirect standard error to match the new standard out, so our +errors get dumped to our terminal instead of the file. + +Confusing right? Lucky for us, there's a short form that redirects both at the +same time, making this mistake impossible: ``` $ rustc foo.rs &> foo.log ``` -[Where can I expect to use that?](conversation://Mara/hmm) +This will put standard out and standard error to `foo.log` the same way that +`> foo.log 2>&1` will. -[It's a bourne shell extension, but I've tested it in `zsh` and `fish`. You can -also do `&|` to pipe both standard out and standard error at the same time in -the same way you'd do `2>&1 | whatever`.](conversation://Cadey/enby) +[Will that work in every shell?](conversation://Mara/hmm) -That will put standard out and standard error to `foo.log` the same way that -`2>&1 > foo.log` will. You can also use this with `>>`: +[It's a bourne shell (`bash`) extension, but I've tested it in `zsh` and `fish`. +You can also do `&|` to pipe both standard out and standard error at the same +time in the same way you'd do `2>&1 | whatever`.](conversation://Cadey/enby) + +You can also use this with `>>`: ``` $ rustc foo.rs &>> foo.log @@ -265,9 +293,13 @@ error: aborting due to previous error [How do I redirect standard in to a file?](conversation://Mara/hmm) -The answer there is not directly! There is a workaround in the form of a tool -called `tee` which outputs its standard in to both standard out and a file. For -example: +Well, you don't. Standard in is an input, so you can change where it comes +_from_, not where it goes. + +But, maybe you want to make a copy of a program's input and send it somewhere +else. There is a way to do _that_ using a command called `tee`. `tee` copies +its standard input to standard output, but it also writes a second copy to a +file. For example: ```console $ dmesg | tee dmesg.txt | grep 'msedge' @@ -345,6 +377,6 @@ What else could you do with pipes and redirection? The cloud's the limit! --- -Thanks to violet spark for looking over this post and fact-checking as well as -helping mend some of the brain dump and awkward wording into more polished -sentences. +Thanks to violet spark, cadence, and AstroSnail for looking over this post and +fact-checking as well as helping mend some of the brain dump and awkward +wording into more polished sentences.