命令行界面 (CLI)、终端 (Terminal)、Shell、TTY的区别

命令行界面 (CLI) = 使用文本命令进行交互的用户界面
终端 (Terminal) = TTY = 文本输入/输出环境
控制台 (Console) = 一种特殊的终端
Shell = 命令行解释器,执行用户输入的命令并返回结果



  1. 什么是命令行界面?
    命令行界面,通俗来讲,就是你看过的那种满屏幕都是字符的界面。

  2. 终端 —— 人与机器交互的接口
    2.2. 控制台 (Console) 是什么?
    在历史上,终端是连接到计算机上的一种带输入输出功能的外设。但是有一个终端与众不同,它与计算机主机是一体的,是计算机的一个组成部分。这个特殊的终端就叫做 控制台(Console)。



一台计算机上一般只有一个控制台,但是可以连接很多个终端。
不过随着个人计算机的普及,控制台 (Console) 与终端 (Terminal) 的概念已经逐渐模糊。在现代,我们的键盘与显示器既可以认为是控制台,也可以认为是普通的终端。当你在管理系统时,它们是控制台;当你在做一般的工作时(浏览网页、编辑文档等),它们就是终端。我们自己既是一般用户,也是系统管理员。



因此,现在 Console 与 Terminal 基本被看作是同义词。



2.3. 终端模拟器 (Terminal Emulator)
随着计算机的进化,我们已经见不到专门的终端硬件了,取而代之的则是键盘与显示器。



但是没有了终端,我们要怎么与那些传统的、不兼容图形接口的命令行程序(比如说 GNU 工具集里的大部分命令)交互呢?这些程序并不能直接读取我们的键盘输入,也没办法把计算结果显示在我们的显示器上……(图形界面的原理我这里就不多说了,它们编程的时候图形接口还在娘胎里呢!)



这时候我们就需要一个程序来模拟传统终端的行为,即 终端模拟器 (Terminal Emulator)。



严格来讲,Terminal Emulator 的译名应该是「终端仿真器」
对于那些命令行 (CLI) 程序,终端模拟器会「假装」成一个传统终端设备;而对于现代的图形接口,终端模拟器会「假装」成一个 GUI 程序。一个终端模拟器的标准工作流程是这样的:



捕获你的键盘输入;
将输入发送给命令行程序(程序会认为这是从一个真正的终端设备输入的);
拿到命令行程序的输出结果(STDOUT 以及 STDERR);
调用图形接口(比如 X11),将输出结果渲染至显示器。
终端模拟器有很多,这里就举几个经典的例子:



GNU/Linux:gnome-terminal、Konsole;
macOS:Terminal.app、iTerm2;
Windows:Win32 控制台、ConEmu 等。



2.4 终端窗口 (Terminal Window) 与虚拟控制台 (Virtual Console)
大部分终端模拟器都是在图形用户界面 (GUI) 中运行的,但是也有例外。



比如在 GNU/Linux 操作系统中,按下 Ctrl + Alt + F1,F2…F6 等组合键可以切换出好几个黑不溜秋的全屏终端界面,而按下 Ctrl + Alt + F7 才是切换回图形界面。不过不要被它们唬着了,虽然它们并不运行在图形界面中,但其实它们也是终端模拟器的一种。




  1. 那么 TTY 又是什么?
    简单来说,tty 就是终端的统称。
    最早的 Unix 终端是 ASR-33 电传打字机。而电传打字机 (Teletype / Teletypewriter) 的英文缩写就是 tty,即 tty 这个名称的来源。



由于 Unix 被设计为一个多用户操作系统,所以人们会在计算机上连接多个终端(在当时,这些终端全都是电传打字机)。Unix 系统为了支持这些电传打字机,就设计了名为 tty 的子系统(没错,因为当时的终端全都是 tty,所以这个系统也被命名为了 tty,就是这么简单粗暴),将具体的硬件设备抽象为操作系统内部位于 /dev/tty* 的设备文件。




  1. Shell —— 提供用户界面的程序
    大家都知道,操作系统有一个叫做 内核 (Kernel) 的东西,它管理着整台计算机的硬件,是现代操作系统中最基本的部分。但是,内核处于系统的底层,是不能让普通用户随意操作的,不然一个不小心系统就崩溃啦!



但我们总还是要让用户操作系统的,怎么办呢?这就需要一个专门的程序,它接受用户输入的命令,然后帮我们与内核沟通,最后让内核完成我们的任务。这个提供用户界面的程序被叫做 Shell (壳层)。



其实 Shell 只是提供了一个用户操作系统的入口,我们一般是通过 Shell 去调用其他各种各样的应用程序,最后来达成我们的目的。比如说我们想要知道一个文件的内容,我们会在 Shell 中输入命令 cat foo.txt,然后 Shell 会帮我们运行 cat 这个程序,cat 再去调用内核提供的 open 等系统调用来获取文件的内容。虽然并不是 Shell 直接去与内核交互,但广义上可以认为是 Shell 提供了与内核交互的用户界面。



Shell 通常可以分为两种:命令行 Shell 与 图形 Shell。顾名思义,前者提供一个命令行界面 (CLI),后者提供一个图形用户界面 (GUI)。Windows 下的 explorer.exe 就是一个典型的图形 Shell(没错,它确实是,因为它接受来自你的指令,并且会帮你与内核交互完成你的指令)。



常见或历史上知名的命令行 Shell 有:



适用于 Unix 及类 Unix 系统:
sh (Bourne shell),最经典的 Unix shell;
bash (Bourne-Again shell),目前绝大多数 Linux 发行版的默认 shell;
zsh (Z shell),我个人最喜欢的 shell;
fish (Friendly interactive shell),专注于易用性与友好用户体验的 shell;
Windows 下的 cmd.exe (命令提示符) 与 PowerShell。




  1. Shell 与终端的分工
    现在我们知道,终端干的活儿是从用户这里接收输入(键盘、鼠标等输入设备),扔给 Shell,然后把 Shell 返回的结果展示给用户(比如通过显示器)。而 Shell 干的活儿是从终端那里拿到用户输入的命令,解析后交给操作系统内核去执行,并把执行结果返回给终端。



不过 Shell 与终端的分工有一些容易混淆的地方,这里以例子进行说明:



终端将用户的键盘输入转换为控制序列(除了字符以外的按键,比如 左方向键 → ^[[D),Shell 则解析并执行收到的控制序列(比如 ^[[D → 将光标向左移动);
不过也有例外,比如终端在接收到 Ctrl + C 组合键时,不会把这个按键转发给当前的程序,而是会发送一个 SIGINT 信号(默认情况下,这会导致进程终止)。其他类似的特殊组合键有 Ctrl-Z 与 Ctrl-\ 等,可以通过 stty -a 命令查看当前终端的设置。



Shell 发出类似「把前景色改为红色(控制序列为 \033[31m)」「显示 foo」等指令;
终端接收这些指令,并且照着 Shell 说的做,于是你就看到了终端上输出了一行红色的 foo。



除非被重定向,否则 Shell 永远不会知道它所执行命令的输出结果。我们可以在终端窗口中上下翻页查看过去的输出内容,这完全是终端提供的 feature,与 Shell 没有半毛钱关系;
命令提示符 (Prompt) 是一个完全的 Shell 概念,与终端无关;
行编辑、输入历史与自动补全等功能是由 Shell 提供的(比如 fish 这个 Shell 就有着很好用的历史命令与命令自动补全功能)。不过终端也能自己实现这些功能,比如说 XShell 这个终端模拟器就可以在本地写完一行命令,然后整条发送给远程服务器中的 Shell(在连接状况不佳时很有用,不然打个字都要卡半天);
终端中的复制粘贴功能(Shift + Insert 或者鼠标右键等)基本上都是由终端提供的。举个例子,Windows 默认的终端对于复制粘贴的支持很屎,而换一个终端(例如 ConEmu)后就可以很好地支持复制粘贴。不过 Shell 以及其他命令行程序也可以提供自己的复制粘贴机制(例如 vim)。



#include
#include
#include
#include
#include
#include <sys/types.h>
#include <sys/stat.h>



#define TTY0 “/dev/pts/0”
#define TTY1 “/dev/pts/12”
#define TTY2 “/dev/pts/14”
#define TTY3 “/dev/pts/3”



void tty_write(char* tty,char* buff)
{
int fd=-1;
fd=open(tty,O_RDWR);
if(fd<0)
{
printf(“open errror\n”);
//perror(“open tty error\n”);
exit(-1);
}
write(fd,buff,strlen(buff));
close(fd);
}



int main(int argc, char const *argv[])
{
char buff[]=”test tty… \n”;
char tty[32]={0};
strcpy(tty,TTY1);
tty_write(tty,buff);
strcpy(tty,TTY2);
tty_write(tty,buff);



strcpy(tty,TTY0);
tty_write(tty,buff);

return 0; }


//msg_send.c
#include
#include
#include
#include
#include
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>



#define MAX_TEXT 512
typedef struct my_msg_st{
long int my_msg_type;
char some_text[MAX_TEXT];
} my_msg_st;



//Linux c消息队列的使用
int main(int argc, char const argv[])
{
int running=1;
my_msg_st some_data;
int msgid;
char buffer[BUFSIZ];
printf(“BUFSIZ=%d\n”, BUFSIZ);
msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
fprintf(stderr, “msgget failed with error:%s\n”, strerror(errno));
exit(-1);
}
while(running)
{
printf(“enter some text:\n”);
fgets(buffer,BUFSIZ,stdin);
some_data.my_msg_type=1;//初始化消息类型
strcpy(some_data.some_text,buffer);
//添加到消息队列
if(msgsnd(msgid,(void
)&some_data,MAX_TEXT,0)==-1)
{
fprintf(stderr, “msgsnd failed\n”);
exit(-1);
}
if(strncmp(buffer,”end”,3)==0)
{
running=0;
}
system(“ipcs -q”);
}
return 0;
}



//msg_recv.c
#include
#include
#include
#include
#include
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>



#define MAX_TEXT 512
typedef struct my_msg_st{
long int my_msg_type;
char some_text[MAX_TEXT];
} my_msg_st;



//Linux c消息队列的使用
int main(int argc, char const *argv[])
{
int running=1;
my_msg_st some_data;
int msgid;
long int msg_to_receive=0;//消息类型



msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
fprintf(stderr, "msgget failed with error:%s\n", strerror(errno));
exit(-1);
}
while(running)
{

//从消息队列获取消息,要指定消息类型
if(msgrcv(msgid,(void*)&some_data,MAX_TEXT,msg_to_receive,0)==-1)
{
fprintf(stderr, "msgsnd failed\n");
exit(-1);
}
printf("You write:%s\n", some_data.some_text);
if(strncmp(some_data.some_text,"end",3)==0)
{
running=0;
}

}
if(msgctl(msgid,IPC_RMID,0)==-1)
{
fprintf(stderr, "msgctl IPC_RMID failed\n");
exit(-1);
}

return 0; }

Category linux