Each language has its own way of printing results on the screen:
javascript
console.log('hello!');
php
<?php
echo 'hello!';
python
print('hello!')
java
System.out.print("hello!");
ruby
puts 'hello!'
Despite the variety of languages and printing methods, from the point of view of the operating system that runs the program, they all work exactly the same. When any program starts, the operating system associates three so-called threads with it: STDIN (Standard Input), STDOUT (Standard Output) STDERR (Standard Error). A programming language considers streams as files and interacts with them as files. STDOUT is responsible for the output on the screen. Every time a program (in any language) prints to the screen, the print function actually writes data to STDOUT using the write
function, and it's up to the operating system to decide where to output the result. The default output is on the terminal screen.
Here it must be said that a good understanding of this topic requires knowledge of operating systems, in particular, knowledge of the subsystems responsible for processes and the file system. In a nutshell, no programming language can knows that a screen exists, much less interact with it. The responsibility for interacting with the hardware rests entirely on the shoulders of the operating system, and the programs can only ask the operating system to perform a particular task. Once you understand this division, implementing programming languages is greatly simplified. It's enough to know about the existence of STDOUT and be able to write to it, the operating system will do everything else. This means that a program written on one computer can run without problems on another computer with a different configuration and monitor (or even without a monitor altogether).
Now comes the interesting part. The OS allows these threads to be swapped out at system startup, which opens up some interesting possibilities. For example, the output of any command running in bash can be written to a file instead of being displayed on the screen.
ls -la > output
When you run this command, you'll see that nothing appears on the screen, instead, an output file will appear in the current directory.
cat output
total 44
drwxr-xr-x 5 kirill.m kirill.m 4096 Aug 29 09:39 .
drwxr-xr-x 8 root root 4096 Apr 26 10:38 ..
-rw------- 1 kirill.m kirill.m 1822 Aug 29 08:45 .bash_history
-rw-r--r-- 1 kirill.m kirill.m 220 Aug 31 2015 .bash_logout
-rw-r--r-- 1 kirill.m kirill.m 3771 Aug 31 2015 .bashrc
drwx------ 2 kirill.m kirill.m 4096 Mar 30 18:10 .cache
-rw------- 1 kirill.m kirill.m 55 Aug 28 18:49 .lesshst
drwxrwxr-x 2 kirill.m kirill.m 4096 Aug 29 08:35 .nano
-rw-rw-r-- 1 kirill.m kirill.m 0 Aug 29 09:39 output
-rw-r--r-- 1 kirill.m kirill.m 655 May 16 2017 .profile
drwx------ 2 kirill.m kirill.m 4096 Jan 22 2018 .ssh
-rw------- 1 kirill.m kirill.m 513 Aug 29 08:06 .viminfo
The operation we did above is called thread redirection. The >
symbol tells the system to take the output from the command specified on the left and send it to the file specified on the right. >
always overwrites the file. This redirection works with every other command that outputs its results to the console.
grep alias .bash_profile > result
cat result
alias fixssh='eval $(tmux showenv -s SSH_AUTH_SOCK)'
If you don't want to overwrite it, and instead add to it, use >>
.
When experimenting with output, it's convenient to use the echo
command built into the shell. It takes a string as input and outputs it to STDOUT, which can be redirected.
# > rewrite file
echo 'hi' > result
cat result
hi
echo 'hello' > result
cat result
hello
# >> adds content to the end of the file
echo 'hello' >> result
cat result
hello
hello
In addition to standard output, two additional streams are associated with each process: STDIN (standard input) and STDERR (error output). STDIN works in the opposite direction. By using it, the program can receive data to input. On *nix systems, there is a built-in wc
(word count) utility that can count the number of words, lines, or characters in a file. Whenever we talk about a file, in *nix it almost always means that data can be transferred to a standard input stream.
# The flag l (l, not 1) tells the system to count the number of lines
wc -l < result
2
It looks pretty logical; the arrow reverses its direction, and the contents of the file are sent to the STDIN of the wc
program being run. Now let's do a trick and combine the redirection of input and output.
wc -l < result > output
cat output
2
By the way, you can print output in the same way, but I'll leave that for you to do in your own time.
The last question relates to why we need the STDERR thread. It, like STDOUT, goes to the screen by default. STDERR allows you to separate the normal output of a program from the errors that occur. This approach is useful for logging, reacting and debugging. Be careful, redirecting the output to a file only redirects STDOUT. We can show you this very easily. If you try to display the contents of a directory that does not exist, the ls
command will generate an error:
ls lala
ls: cannot access 'lala': No such file or directory
Now let's try to redirect the output to the output file
ls lala > output
ls: cannot access 'lala': No such file or directory
A redirect is in place, but a message will be displayed. This happened precisely because STDERR was still tied to the screen, and there was no output file. There are several ways to solve this problem. For example, by redirecting STDERR to STDOUT, or by sending them both to a file.
Redirecting STDERR to STDOUT
Often the standard error stream is combined with the standard output stream, so you can handle errors and execution results together. For example:
# First STDOUT is redirected to the file, then STDERR is redirected to STDOUT, continuing to write to the file
ls lala > output 2>&1
cat output
ls: cannot access 'lala': No such file or directory
2>&1
written before > output
will not work, because when the interpreter reads 2>&1
, it doesn't yet know where the standard output stream has been redirected, so the error and output streams will not be combined:
ls lala 2>&1 > output
ls: cannot access 'lala': No such file or directory
In Unix, each thread has a number, which is a file descriptor for input and output threads. 2
in this case stands for the number of the STDERR thread. These are the standard I/O streams: STDIN - 0, STDOUT - 1, STDERR - 2. In shells descended from C Shell, the syntax rules require the addition of an ampersand after the redirect character to specify the thread to redirect to.
The STDERR redirect is sometimes useful on its own without an output to a file.
# STDERR is simply redirected to another thread (STDOUT)
cd lala 2>&1
-bash: cd: lala: No such file or directory
Redirecting a specific stream to a file
To redirect a particular thread, you need to specify its number before >
.
# This way you can redirect STDERR directly to the file
cd lala 2> output
cat output
-bash: cd: lala: No such file or directory
Redirecting both threads to a file
This option is the most commonly used because it makes it easier to debug and understand why some error or another occurred in the first place.
# Both streams, STDERR and STDOUT, are redirected to the file
cd lala &> output
cat output
-bash: cd: lala: No such file or directory