深入理解 shell/bash

Posted 正义的伙伴啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解 shell/bash相关的知识,希望对你有一定的参考价值。

文章目录

什么是shell和bash

什么是shell?

我们现在使用的Linux操作系统其实是一个宏观上的操作系统,严格的来说我们现在使用的操作系统是 shell+linux内核(kernel) +驱动程序

也就是作为上层用户,我们无法与kernel直接交互的,必须通过shell与内核沟通,而shell也就是我们在登录linux时看到的命令行解释器,而shell主要有两个功能:

  1. 将用户请求任务输入的指令,“翻译”成操作系统能听懂的语言
  2. 将操作系统对应的执行结果“翻译”成用户能看懂的语言

可以将shell理解成一个翻译官,用户 和 kernel 说着互相都不会的语言,shell在其中充当翻译,同时还起着保镖的作用,防止用户对内核kernel进行胡乱修改(如果用户可以随意修改kernel,后果会不堪设想)

什么是bash

bash是shell的具体化,shell是对bash的统称,我们可以输入echo $SHELL 来查看目前我们的Linux所使用的的shell的类型

我这台机器使用的是bash命令行解释器。那么bash作为linux系统的shell(命令行解释器)有什么优点吗?

  1. 首先可以通过输入history 来查看历史命令,这些历史命令存在家目录.bash_history中,在你注销系统的时候才会将你在本次登录期间所有输入的历史命令写入这个文件,所以你输入history 是无法看到本次登录期间的历史命令,但是可以通过⬆⬇键来查看本次登录期间历史命令
  2. tab补全命令,如果你输入一个命令到一半忘记了后一半,可以按两下tab,bash会将所有可能的指令的名称显示出来
  3. 你也可以通过alias对命令设置别名,例如alias lm = 'ls -al' 以后键入lm就相当于输入了ls -al,不过这个别名也仅限于当前登录,如果你退出终端再次登录并输入lm时,bash就不识别lm了,接下来的配置bash会告诉你如何设置一个永久的变量别名
  4. shell script ——程序化脚本

更多关于bash的信息,你可以在你的终端输入man bash来进行查看,如果细心的人一定会发现man bash中有关于cd echo umask…等 指令的解释,这些指令bash的内置命令!!,内置命令再调用的时候是不用在创建子进程的!!(后面会用到)
如何查看内置命令——type
type 指令
就可以看到是否是内置命令

深入理解shell的变量

指令 or 可执行程序?


我们创建一个test.c,对其编译连接生成可执行程序:a.out,如果我们要执行这个可执行程序就要输入指令./a.out ,这里就要说明一下这个./a.out 实际上就是a.out的路径(.是当前目录的路径的简写)。
而我们平时执行的ls命令,如果我们输入他的路径/bin/ls你会发现和直接输入ls输出的结果一样。这样我们就得到了两个结论:

  1. lsa.out 本质都是一样的 ,都是一个可执行程序!
  2. ls 可以不用输入他的路径就可以执行,而a.out不行(直接输入a.out会显示command not found)

这里就涉及到了变量的概念!这里用到的变量是:环境变量PATH(指定命令的搜索路径)
这里变量的概念和C语言的概念大致相同,简单来说就是给变量起一个简单的别名,每个变量都有自己的值,可以通过echo $变量名,记住一定要加$符号,否则打印出的是echo后的内容(变量名),而不是变量的内容
这里我们输入echo $PATH

PATH的值就显示出来了,我们发现PATH的值是由:隔开的一个一个路径,这也就能解释清楚为什么ls不用输入完整的路径名:我们在输入一个指令(可执行程序名)时,bash就会在PATH包含的目录里一个一个按顺序找有没有与之名字匹配的可执行程序,如果找到就执行该指令(可执行程序)。 因为a.out的路径不在PATH变量的内容中,所以单纯键入a.outbash就会在PATH众多路径中寻找,但是结果就是找不到!

如果我们将a.out的路径添加到PATH中就会解决这种问题
PATH=$PATH:~/linux/a.out 这里~/linux/a.out是我的a.out的路径,就可以把路径添加到PATH中
这时在查看PATH,添加成功😋

这里输入a.out就会直接执行

但是这种方法有一个坏处就是:如果你现在立刻退出再重新登录终端,你在输入a.out试试?就会发现不行了,你在输入echo $PATH时,你就会发现你添加的a.out路径没了😅,不急后面的配置自己的bash/shell会详细说明

环境变量 or 本地变量

环境变量
上面说到的PATH就是一个环境变量,这种变量与bash强相关,可以为我们操作节省许多不必要的麻烦:如果每次输入指令例如ls,都要带上他的目录会不会很麻烦?

我们可以通过envexport来查看,bash的环境变量
其中有几个比较重要的环境变量:

  1. PATH:前面已经说过了
  2. HOME:指定用户的主工作目录(即用户登录到linux系统中时,默认的目录)
  3. SHELL:当前使用的shell类型:bash
  4. HISTSIZE:这个与历史命令有关,即我们曾经执行过的命令可以被系统记录下来,而记录的条数由这个变量控制
    可以看出环境变量和系统强相关

本地变量

  1. 如何查看 ?
    输入set,但是set显示的所有变量包括 本地变量和环境变量
  2. 如何定义?
    变量名='变量内容' 就可以定义一个变量(具体定义放在变量操作)

两者的区别
环境变量:子进程也可以调用
本地变量:只能在当前shell命令行解释器内被访问,不可以被子进程继承

创建一个test.cpp,用getnev函数来查看myname变量是否被子进程继承,这里用到getenv()函数输入变量名返回的是变量的名称

#include<iostream>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 using namespace std;
  5 int main()
  6 
  7   cout<<getenv("myname")<<endl; //getenv(变量名)返回变量的内容                                 
  8   return 0;                                          
  9              

编译成可执行程序p,这时bash就会创建一个子进程,执行可执行程序p,结果发现没有输出,说明myname并没有被p继承

那你一定非常好奇为什么环境变量能被继承?

  1. 命令行的三个参数

我们可以遍历env数组指针中的字符串:

#include<stdio.h>
    2 #include<unistd.h>
    3 #include<stdlib.h>
    4 int main(int argc,char* argv[],char *env[])
    5 
    6 
    7   for(int i=0;env[i];i++)
    8     printf("%s\\n",env[i]);                                                                   
    9   return 0;
   10 

但是注意编译的时候 gcc test2.c -o p -std=c99 要以C99的方式编译,否则就会报错
执行./p时,你会发现输出的和 env输出的环境变量一模一样

那什么是命令行参数?

打个比方:ls 是显示当前目录的文件,而ls -l是以行的模式输出当前目录的文件,而ls -l -a 是以行的模式输出当前目录的文件包括隐藏文件

由于前面已经证明ls就是一个可执行程序,这里ls后面的 -l -a就是命令行参数,所以我们也可以用main来实现一个
argc表示的是 指令后面参数的个数+1 (指令本身也算一个)
argv[]存储的是 指令后面参数的字符串
所以argc默认为1,argv[0]存储的是可执行程序(指令)的字符串,

#include<stdio.h>
  2 #include<string.h>
  3 int main(int argc,char* argv[])
  4 
  8   if(argc > 1)
  9   
 10     for(int i=1;i<argc;i++)
 11     
 12       if(strcmp(argv[i],"-a")==0)
 13         printf("执行a选项\\n");
 14       else if(strcmp(argv[i],"-b")==0)
 15         printf("执行-b选项\\n");
 16     
 17   
 18   else
 19   
 20     printf("没有命令行参数\\n");
 21   
 22 
 23              

变量的操作

变量的定义

  1. 方法一:变量名=变量内容
    这种定义方法一定要注意等号左右是不能出现空格,否则就会定义失败
  2. 方法二:变量名='变量内容'
    这种定义方式变量内容中就可以出现空格了!但是单引号里的内容是纯文本内容,不会发生转义
  3. 方法三:变量名="变量内容"
    双引号和单引号功能大致相同,但是双引号里面的特殊字符可以保持原有的特性
    这里双引号里的内容是环境变量的内容,而单引号则是单引号里的纯文本内容
  4. 方法三:在已知变量后面添加内容:变量1=$变量2:变量内容
    首先是变量2 必须是已经定义过的变量,其次是 变量内容 如果不加单/双引号是不能出现空格的!

export / unset

exportexport 自定义变量=环境变量,export自定义变量后会变成环境变量,单独输入export命令可以查看所有环境变量
unsetunset 变量 就是删除变量

命令提示符

  1. $ 这个符号本身也是一个变量,代表的是bash的进程号(PID),你可以通过echo $$去查看
  2. ? 上条执行命令的返回值,也可以理解成上一个进程的返回值,重点是上一条返回值,一般进
    正常运行结束就会返回0,如果执行过程中发生错误就会返回一个非零的值

变量内容的删除、取代与替换

  1. 删除 ###%%%
    #:从左到右,符合替换文字的 最短那一个——书写格式$变量#关键字
    ##:从左到右,符合替换文字的最长 那一个——书写格式$变量##关键字
    %:从右到左,符合替换文字的 最短那一个——书写格式$变量%关键字
    %%:从右到左,符合替换文字的最长 那一个——书写格式$变量%%关键字
    /: 将 第一个旧字符串 被新字符串替换 ——书写格式$变量/旧字符串 /新字符串
    //:将所有旧字符串都被新字符串替换 ——书写格式$变量//旧字符串 /新字符串

这里删除的过程中还要用到*通配符,例如:我想删除/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/sht/.local/bin:/home/sht/bin中 的/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/sht 部分,我只需要写成PATH=$PATH#/*sht 其中/*sht就代表了要删除的部分,只需要取首部和末尾具有标识意义的字符就可以简写这么长一串。

配置自己的bash/shell

alias/unalias

alias:给指令设置别名,命名格式alias 新指令='旧指令'
unlias:取消对指令设置的别名,格式:unalias 新指令
但是注意:指令的别名和变量完全是两种概念,指令是可以直接执行的本质上是一个可执行程序,而变量需要echo才能调用出来


还记得上面所说的你定义的变量或重命名的指令在你退出终端会自动消失?还有为什么每次登陆Linux会有这一堆已经设置好的环境变量?
这是因为有一些环境配置文件的存在,让bash在启时直接读取这些配置文件,以规划好bash的操作环境。而这些配置文件又分为 全局系统配置文件 以及 用户个人配置

变量配置文件

这里同样分为 全局系统配置文件 以及 用户个人配置

系统配置文件etc/profile

首先警告你最好不要随意修改这个文件,这个是系统整体的设置。这个配置文件会根据用户的UID来决定重要的数据变量,只要用户登录bash系统就一定会访问这个文件!如果你对这个文件内容进行修改,所有用户的内容都会被修改!
输入vim /etc/profile 即可查看该文件内容!其内容大概对PATH MAIL USER HOSTNAME HISTSIZE umask 进行了规定初始化

用户个人配置 ~/bash_profile

etc/profile配置完之后,接下来就是读取个人配置,个人配置有三个目录,优先从上到下读取第一个,剩下的文件就不会读取,不存在就会跳过:
~/.bash_profile
~/bash_login
~/.profile
我们来看一下~/.bash_profile文件的内容,由于这个我文件是隐藏文件,所以必须用ls -al才能看到

1 # .bash_profile
  2 
  3 # Get the aliases and functions
  4 if [ -f ~/.bashrc ]; then
  5   . ~/.bashrc              //读取.bashrc
  6 fi
  7 
  8 # User specific environment and startup programs
  9 
 10 PATH=$PATH:$HOME/.local/bin:$HOME/bin   //用户配置PATH定义,这里如果要表示多个目录的集合,每个目录用:隔开                                                        
 11 
 12 export PATH

如何将一个路径永久添加到PATH

我们将这个代码通过g++ test.c -o exe 生成一个叫exe的可执行程序,下面就有三种方法将他的路径永久添加到PATH中

  1. 直接对PATH进行修改:使用语句:PATH=$PATH:文件所在的目录,但是这种修改下次登录的时候就会消失

  2. 对用户的bash配置文件进行修改:

    • 第一种方法:查看文件.bash_profile文件你就会知道PATH每次在用户登录的时候就会被赋值:PATH=$PATH:$HOME/.local/bin:$HOME/bin,所以我们可以直接在.bash_profile中的PATH后加上文件所在目录PATH=$PATH:$HOME/.local/bin:$HOME/bin:文件所在目录
      例如:
      添加后执行source .bash_profile 相当于关机重启系统,这样重启的时候配置文件才会被读取,你设置的目录才会被添加进去,如果不重启PATH还是修改前的PATH,这时输入exebash就会在PATH所提供的路径中寻 找,这样他就和ls cd一样不用写完整的路径名就可以执行
    • 第二种方法:但是我们注意到 PATH在.bash_profile赋值的时候在系统赋值的PATH的基础上添加了一个$HOME/bin,这是在用户家目录里一个叫bin的目录,所以我们也可以把自己写的可执行程序扔到bin目录里(如果系统没有bin目录自己可以mkdir bin创建一个!我的系统默认就是没有的)

      这时候PATH路径中也包含了可执行的路径,这种修改方式比较推荐,以后只要有要添加的直接扔到bin里面就行了!
  3. 对系统的bash配置文件进行修改: 暂时不建议这么做

指令配置文件 ~/.bashrc

先看看这个用户配置文件的内容

主要是对用户配置中:命令定义别名,所以你可以把一些指令的别名定义在这,每次登陆的时候读取这个问价,你定义的别名就相当于“永久存在”


例如我在后面添加:alias lm='ls -al' 下次输入lm就相当于输入ls -al
我在我的Linux操作系统里面添加了一个指令:alais work='cd 我的工作目录',这样每次启动云服务器,我只要输入work指令就可以快速进入我的工作目录

不过这里要注意在更改~/.bashrc之后指令并不会立即有效而是要重启一下云服务器才行,这验证了这些配置文件都是在系统启动的时候读取的!,如果你不想重启系统,可以执行source ~/.bashrc作为替代

其实在读取这个文件之前还会读取 系统配置文件etc/bashrc,这个文件主要完成了以下任务:

  1. 设置umask值
  2. 设置目录提示符$ ? 等

重定向 和 管道

如果你想了解重定向的底层原理,请移步:,如果想了解管道的底层原理,请移步:
我这里介绍只是在Linux操作系统下基本用法。

重定向

重定向的意思就是将本来应该输出到屏幕上的数据输出到另外一个文件夹里面。但其实重定向还有一个广义定义就是将本来应该输入到一个文件里面的信息输出到另外一个文件中。这两种说法并不矛盾,因为屏幕在操作系统眼中也是文件,但是Linux操作系统的重定向还是以屏幕的三个输入输出流为主(stdout、stdin、stderr)


重定向操作:

  • 标准输出(stdout)>>>
  1. >是将要输入的文件夹的内容清空,然后将数据写入重定向的文件夹,如果>后面的文件夹不存在,那么会创建一个
ls -al > log.txt  //例如我们在bash中输入这行指令就代表将ls -al 的内容输入到log.txt文件夹中
  1. >>——追加重定向>最大的区别在于不会清空>后面文件的内容,而是在文件数据末尾进行追加写入

  • 标准错误(stderr)代码为2(这里学过文件描述符就知道为什么是2了😋),使用2>2>>
    2>2>>会将bash在屏幕上输出的报错信息输出到指定的文件夹中:
chmod 222 log.txt
cat log.txt 2> log.err  //由于log.txt的权限问题这里shell会报错,我们进行重定向将错误信息输出到log.err
cat log.err


  • 标准输入(stdin)<<<
  1. <:这个的意思是将原本应该由键盘输入的数据改由文件内容来替换,例如:
cat > log.txt < log.err  //将log.err的内容输入log.txt中
  1. <<这个字符与上面的字符功能就大相径庭了,<<他代表两个结束输入的字符。也就是我们用键盘进行输入的时候,如果出现<<后面的字符就停止字符输入。例如下面的代码我们用键盘向log.txt中输入数据,当输入的数据中出现eof就停止输入

管道


管道命令:|仅能处理经由前面一个命令传来的正确信息,也就是标准输出的信息,对于标准错误并没有直接处理的能力。而且注意 管道后面的指令并不是所有指令都能用,而是部分“管道指令”,如果你学过 进程间通信的话,对管道的理解会更加深刻,反正要记住管道指令需要输入数据
这些指令要满足:

  • 管道指令必须会处理标准输出,对于标准错误给予忽略
  • 管道命令必须要能够接受来自前一个命令的数据成为标准输入

常见的管道指令:lessmorehead, tailcutgrep

例如:

xargs

这个指令是一个非常容易与管道混淆的概念,但是其实他们之间的区别还是非常大的。xargs就是产生某个命令参数的意思,将xargs前面指令输出在屏幕上的内容当做参数传递给xargs后面的指令。除此之外xargs可以读入stdin的数据,并且以空格符或换行符作为识别符,将stdin的数据分割成为参数

例如:我们要在一个目录里面(这个目录可能有成百上千的文件),我们要在其中显示所有以.txt结尾的文件的详细信息,由于ls -al是无法从文件里面读取数据,而已.txt结尾的数据我们又需要从find -name "*.txt"中得出

很多命令其实不支持管道命令,其本质是这些命令无法接收到标准输入的数据,我们可以通过xargs来提供该命令使用标准输入

以上是关于深入理解 shell/bash的主要内容,如果未能解决你的问题,请参考以下文章

深入理解AJAX

shell,bash,git bash,xshell,ssh

深入讲解丨Shell脚本单行和多行注释

《深入理解ES6》之扩展对象的功能性

图像基础概念与YUV/RGB深入理解

Linux基础之-Bash命令优先级