Most output from commands will be standard output, written to stdout on /dev/fd/1. By using the > symbol, we can redirect this out with the following syntax:
command > output-file
A redirect will always be made to a file (however, as we know, not all files are equal, so after the regular examples, we'll show you some Bash magic where non-regular files are concerned). If the file does not exist, it will be created. If it does exist, it will be overwritten.
In its simplest form, everything that would normally be printed to your Terminal can be redirected to a file:
reader@ubuntu:~/scripts/chapter_12$ ls -l /var/log/dpkg.log
-rw-r--r-- 1 root root 737150 Nov 5 18:49 /var/log/dpkg.log
reader@ubuntu:~/scripts/chapter_12$ cat /var/log/dpkg.log > redirected-file.log
reader@ubuntu:~/scripts/chapter_12$ ls -l
total 724
-rw-rw-r-- 1 reader reader 737150 Nov 5 19:45 redirected-file.log
As you know, cat prints the whole file content to your Terminal. In reality, it actually sends the whole content to stdout, which is bound to /dev/fd/1, which is bound to your Terminal; this is why you see it.
Now, if we redirect the content of the file back to another file, we've essentially made a great effort to... copy a file! From the file sizes you can see that it is actually the same file. If you're unsure, you can use the diff command to see if the files are the same:
reader@ubuntu:~/scripts/chapter_12$ diff /var/log/dpkg.log redirected-file.log
reader@ubuntu:~/scripts/chapter_12$ echo $?
0
If diff does not return any output, and it has an exit code of 0, there are no differences in the file.
Back to the redirection example. We used > to redirect the output to the file. In reality, > is shorthand for 1>. You might recognize this 1: it refers to the file descriptor /dev/fd/1. As we'll see when we're dealing with stderr, which is on /dev/fd/2, we will use 2> instead of 1> or >.
First, however, let's build a simple script to illustrate this a little bit further:
reader@ubuntu:~/scripts/chapter_12$ vim redirect-to-file.sh
reader@ubuntu:~/scripts/chapter_12$ cat redirect-to-file.sh
#!/bin/bash
#####################################
# Author: Sebastiaan Tammer
# Version: v1.0.0
# Date: 2018-11-05
# Description: Redirect user input to file.
# Usage: ./redirect-to-file.sh
#####################################
# Capture the users' input.
read -p "Type anything you like: " user_input
# Save the users' input to a file.
echo ${user_input} > redirect-to-file.txt
Now, when we run this, read will prompt us to input some text. This will be saved in the user_input variable. Then, we'll use echo to send the content of the user_input variable to stdout. But, instead of it reaching the Terminal on /dev/pts/0 via /dev/fd/1, we redirect it to the redirect-to-file.txt file.
All in all, it looks something like this:
reader@ubuntu:~/scripts/chapter_12$ bash redirect-to-file.sh
Type anything you like: I like dogs! And cats. Maybe a gecko?
reader@ubuntu:~/scripts/chapter_12$ ls -l
total 732
-rw-rw-r-- 1 reader reader 737150 Nov 5 19:45 redirected-file.log
-rw-rw-r-- 1 reader reader 383 Nov 5 19:58 redirect-to-file.sh
-rw-rw-r-- 1 reader reader 38 Nov 5 19:58 redirect-to-file.txt
reader@ubuntu:~/scripts/chapter_12$ cat redirect-to-file.txt
I like dogs! And cats. Maybe a gecko?
Now, this works as advertised. However, if we run it again, we see two things that can go wrong with this script:
reader@ubuntu:~/scripts$ bash chapter_12/redirect-to-file.sh
Type anything you like: Hello
reader@ubuntu:~/scripts$ ls -l
<SNIPPED>
drwxrwxr-x 2 reader reader 4096 Nov 5 19:58 chapter_12
-rw-rw-r-- 1 reader reader 6 Nov 5 20:02 redirect-to-file.txt
reader@ubuntu:~/scripts$ bash chapter_12/redirect-to-file.sh
Type anything you like: Bye
reader@ubuntu:~/scripts$ ls -l
<SNIPPED>
drwxrwxr-x 2 reader reader 4096 Nov 5 19:58 chapter_12
-rw-rw-r-- 1 reader reader 4 Nov 5 20:02 redirect-to-file.txt
The first thing that goes wrong, which we've warned about before, is that relative paths might mess up where the file is written.
You might have envisioned that the file was created right next to the script; this will only happen if your current working directory is in the directory where the script is. Because we call it from lower in the tree, the output is written there (since that is the current working directory).
The other problem is that each time we type something in, we remove the old content of the file! After we type Hello, we see that the file is six bytes (one byte for each character, plus a newline), and after we typed Bye, we now see that the file is only four bytes (three characters plus the newline).
This might be the desired behavior, but more often than not it is much nicer if the output is appended to the file, instead of replacing it.
Let's solve both issues in a new version of the script:
reader@ubuntu:~/scripts$ vim chapter_12/redirect-to-file.sh
reader@ubuntu:~/scripts$ cat chapter_12/redirect-to-file.sh
#!/bin/bash
#####################################
# Author: Sebastiaan Tammer
# Version: v1.1.0
# Date: 2018-11-05
# Description: Redirect user input to file.
# Usage: ./redirect-to-file.sh
#####################################
# Since we're dealing with paths, set current working directory.
cd $(dirname $0)
# Capture the users' input.
read -p "Type anything you like: " user_input
# Save the users' input to a file. > for overwrite, >> for append.
echo ${user_input} >> redirect-to-file.txt
Now, if we run it (from wherever), we'll see that new text gets appended to the first sentence, I like dogs! And cats. Maybe a gecko? in the /home/reader/chapter_12/redirect-to-file.txt file:
reader@ubuntu:~/scripts$ cd /tmp/
reader@ubuntu:/tmp$ cat /home/reader/scripts/chapter_12/redirect-to-file.txt
I like dogs! And cats. Maybe a gecko?
reader@ubuntu:/tmp$ bash /home/reader/scripts/chapter_12/redirect-to-file.sh
Type anything you like: Definitely a gecko, those things are awesome!
reader@ubuntu:/tmp$ cat /home/reader/scripts/chapter_12/redirect-to-file.txt
I like dogs! And cats. Maybe a gecko?
Definitely a gecko, those things are awesome!
So, cd $(dirname $0) helped us with our relative paths, and a >> instead of > ensured appending instead of overwriting. As you might expect, >> is again short for 1>>, as we will see when we start redirecting stderr streams in a bit.
A little while back, we promised you some Bash magic. While not exactly magic, it might hurt your head just a little:
reader@ubuntu:~/scripts/chapter_12$ cat redirect-to-file.txt
I like dogs! And cats. Maybe a gecko?
Definitely a gecko, those things are awesome!
reader@ubuntu:~/scripts/chapter_12$ cat redirect-to-file.txt > /dev/pts/0
I like dogs! And cats. Maybe a gecko?
Definitely a gecko, those things are awesome!
reader@ubuntu:~/scripts/chapter_12$ cat redirect-to-file.txt > /dev/fd/1
I like dogs! And cats. Maybe a gecko?
Definitely a gecko, those things are awesome!
reader@ubuntu:~/scripts/chapter_12$ cat redirect-to-file.txt > /dev/fd/2
I like dogs! And cats. Maybe a gecko?
Definitely a gecko, those things are awesome!
So, we've managed to print our file using cat a total of four times. We could have done that with for as well, you might be thinking, but the lesson is not the amount of times we printed the message, but how we did it!
First, we just used cat; nothing special there. Next, we used cat in combination with a redirection of stdout to /dev/pts/0, our Terminal. Again, the message is printed.
The third and fourth times, we sent the redirected stdout of cat to /dev/fd/1 and /dev/fd/2. Since these are symlinked to /dev/pts/0, it's not really surprising that these also end up on our Terminal.
How then do we actually differentiate between stdout and stderr?