When writing scripts for Linux from time to time I'm running into problems with TTY like this one:
sudo: sorry, you must have a tty to run sudo
I've had couple of attempts already to figure out what TTY exactly is, but I never grasped the concept fully. Hopefully this time was the last one..
History of TTY
In old days TTY (tele-typewriter) was a typewriter connected via a wire to another typewriter. So when typing something on your end you could've send the same text to someone far away. And on the receiving end the typewriter was typing the same thing on the paper too. Here's a great video explaining how it worked.
With the evolution of computers remote access became a necessity. But those folks didn't have a laptop to SSH to the "server". And so they hooked up tele-typewriters, which made it possible to type something on one end - and that would send an electrical signal over the wire to the computer. It's hard to imagine today, but the first display was.. a roll of paper! You typed something on a TTY and you saw the text you just typed. And then a response came from the computer - and it also was getting typed on your "screen". See this demo (although it has Linux machine on the other end). Additionally a punched tape was used for data storage - you could feed it into a TTY and it would type whatever was encoded on the tape.
At some point TTYs were replaced with computer terminals (e.g. VT05). These were fat clients - you could've edited the text before transferring it all to the computer. And while those were electronic - they still emulated TTYs when communicating with the mainframe.
Interestingly, because typewriters were used for remote control in the early days, people had to use Line Feed and Carriage Return buttons (typewriter had 2 separate buttons to bring the carriage to the next line and move it to the 1st column). To this day (2021) Windows uses them 2 symbols for line breaks: \r\n. While Linux & MacOS moved on to just \n.
TTY in Linux
While actual TTYs are not used anymore - *nix systems keep using it as an abstraction for serial communication. When you work in a terminal - it doesn't just listen to keyboard events and sends them directly to bash or other programs. Instead it uses an intermediary - a pseudo-tty (aka pty or pts for pseudo-teletypes). The program (shell or anything else that you started in the shell) listens to a pty and receives keyboard strokes from it:
And vice versa: when our program writes something into console, it actually writes into pty instead of directly sending data to the terminal:
The reason to have a pty as an intermediate instead of transferring keyboard events directly to the program is that it makes it easier to implement the same mechanism over network:
So there's no need to come up with a new mechanism to transfer data as an input to the command line program. Or to transfer data back from the program (output).
File Descriptors and Standard Input/Output
When opening files we use file names, but once opened OS assigns an ID to the opened file - a file descriptor (fd). In C you'd pass this fd to function read() and write() so that Kernel would read from/write to that file. Besides explicitly opened files, any program must have 3 file descriptors opened when it starts:
0 - user input. Our programs read from this fd when we user types something (like password) into a terminal.
1, 2 - usual output and error output of our programs is written to these "files"
In Java when we work with System.in, System.out, System.err JVM actually works with these 0, 1, 2 file descriptors.
In Linux a File Descriptor doesn't necessarily point to a file, there could be other options. E.g. the output of the program can go to:
Ordinary file
echo 'write this to regular file' > /home/user/out.txt
fd:1 will reference /home/user/out.txt
Pipe
echo 'pass this to the next command' | grep pass`
fd:1 of echo will reference pipe:[some number here]. fd:0 of grep will also reference the same pipe:[some number here].
TTY:
echo 'shown this in the terminal'
All the fd's will reference /dev/pts/[some number]. And our terminal can read from there (and draw the text on the screen), and write there too (user input) - which will be read by the program (well, echo is a bad example as it doesn't read anything from fd:0).
TTY and scripts
So if you're writing a script or a program that reads user input (fd:0) and for some reason you want to ensure it's a real user input in the terminal (as opposed to a file on the disk), you can check it with a command tty -s which returns 0 if fd:0 is a tty:
if $(tty -s); then
echo Enter password:
read password
echo "You entered: $password"
else
echo "You have to enter password, so tty must be enabled"
exit 1
fi
As a result:
$ sh script.sh
Enter password:
While using an ordinary file as an input produces:
$ sh script.sh < password.txt
You have to enter password, so tty must be enabled
Finding logged in users
You can also run a command w (probably stands for who, what, when):
$ w
14:26:50 up 10 days, 21:40, 2 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
stas pts/0 46.32.87.80 14:15 2.00s 0.07s 0.00s w
stas pts/1 46.32.87.80 14:26 2.00s 0.02s 0.02s -bash
As you can see I have 2 sessions open, one uses /dev/pts/0 and the other /dev/pts/1 for TTY. I can even write something to /dev/pts/1 and this will show up in my 2nd session:
echo 'Hello my second session' > /dev/pts/1
You can even chat with someone else logged in on the server, though you'd need sudo for this :)
PS: the real picture with pty's is a little more complicated. When terminal reads/writes - it uses a pty-master, but when a running program reads/writes it uses a pty-slave. These are 2 different "files", but when writing into one - the input will appear in the other.
When writing scripts for Linux from time to time I'm running into problems with TTY like this one:
I've had couple of attempts already to figure out what TTY exactly is, but I never grasped the concept fully. Hopefully this time was the last one..
History of TTY
In old days TTY (tele-typewriter) was a typewriter connected via a wire to another typewriter. So when typing something on your end you could've send the same text to someone far away. And on the receiving end the typewriter was typing the same thing on the paper too. Here's a great video explaining how it worked.
With the evolution of computers remote access became a necessity. But those folks didn't have a laptop to SSH to the "server". And so they hooked up tele-typewriters, which made it possible to type something on one end - and that would send an electrical signal over the wire to the computer. It's hard to imagine today, but the first display was.. a roll of paper! You typed something on a TTY and you saw the text you just typed. And then a response came from the computer - and it also was getting typed on your "screen". See this demo (although it has Linux machine on the other end). Additionally a punched tape was used for data storage - you could feed it into a TTY and it would type whatever was encoded on the tape.
At some point TTYs were replaced with computer terminals (e.g. VT05). These were fat clients - you could've edited the text before transferring it all to the computer. And while those were electronic - they still emulated TTYs when communicating with the mainframe.
Interestingly, because typewriters were used for remote control in the early days, people had to use Line Feed and Carriage Return buttons (typewriter had 2 separate buttons to bring the carriage to the next line and move it to the 1st column). To this day (2021) Windows uses them 2 symbols for line breaks:
\r\n
. While Linux & MacOS moved on to just\n
.TTY in Linux
While actual TTYs are not used anymore - *nix systems keep using it as an abstraction for serial communication. When you work in a terminal - it doesn't just listen to keyboard events and sends them directly to bash or other programs. Instead it uses an intermediary - a pseudo-tty (aka
pty
orpts
for pseudo-teletypes). The program (shell or anything else that you started in the shell) listens to a pty and receives keyboard strokes from it:And vice versa: when our program writes something into console, it actually writes into pty instead of directly sending data to the terminal:
The reason to have a pty as an intermediate instead of transferring keyboard events directly to the program is that it makes it easier to implement the same mechanism over network:
So there's no need to come up with a new mechanism to transfer data as an input to the command line program. Or to transfer data back from the program (output).
File Descriptors and Standard Input/Output
When opening files we use file names, but once opened OS assigns an ID to the opened file - a file descriptor (fd). In C you'd pass this fd to function
read()
andwrite()
so that Kernel would read from/write to that file. Besides explicitly opened files, any program must have 3 file descriptors opened when it starts:0
- user input. Our programs read from this fd when we user types something (like password) into a terminal.1
,2
- usual output and error output of our programs is written to these "files"In Java when we work with
System.in
,System.out
,System.err
JVM actually works with these0
,1
,2
file descriptors.In Linux a File Descriptor doesn't necessarily point to a file, there could be other options. E.g. the output of the program can go to:
Ordinary file
fd:1 will reference
/home/user/out.txt
Pipe
fd:1 of
echo
will referencepipe:[some number here]
. fd:0 ofgrep
will also reference the samepipe:[some number here]
.TTY:
All the fd's will reference
/dev/pts/[some number]
. And our terminal can read from there (and draw the text on the screen), and write there too (user input) - which will be read by the program (well,echo
is a bad example as it doesn't read anything from fd:0).TTY and scripts
So if you're writing a script or a program that reads user input (fd:0) and for some reason you want to ensure it's a real user input in the terminal (as opposed to a file on the disk), you can check it with a command
tty -s
which returns 0 if fd:0 is a tty:As a result:
While using an ordinary file as an input produces:
Finding logged in users
You can also run a command
w
(probably stands for who, what, when):As you can see I have 2 sessions open, one uses
/dev/pts/0
and the other/dev/pts/1
for TTY. I can even write something to/dev/pts/1
and this will show up in my 2nd session:You can even chat with someone else logged in on the server, though you'd need
sudo
for this :)PS: the real picture with pty's is a little more complicated. When terminal reads/writes - it uses a pty-master, but when a running program reads/writes it uses a pty-slave. These are 2 different "files", but when writing into one - the input will appear in the other.