Shell学习笔记

Shell脚本始终是一种弱编程语言,可以的话,尽量用别的语言代替,比如python,环境支持都不错(需要注意2.6和3.x是断崖更新,不兼容。)

认识Shell

  • 只要能够操作应用程序的接口都能被称为Shell。内核kernel在Shell的保护下和用户通信,并操作硬件执行功能。
  • 狭义的Shell指命令行方面的软件。比如bash,各家distribution使用的bash都是一样的,所以有学习价值。
  • Shell的数据流重定向和管道命令等非常有价值。
  • Bourne-Again Shell,简称bash。第一个流行的shell作者是Steven Bourne,.sh的扩展名也是因为他。另外,Linux和Shell都是用C语言写的。
  • 正确地登录Linux之后,才可以真正以Shell来跟Linux通信,这时候就需要有一个bash的执行程序,才能真正经由bash来跟系统通信。

bash特性

  • 命令记忆(history):键盘的上下键。在~/.bash_history这个文件里,保存了前一次登录以前所执行过的命令,容量可以达到1000。当次登录时使用的命令在临时内存里,要注销系统后才保存到文件中。最大的作用是“查询曾经的操作”
  • 命令与文件的补全(tab按键):多按tab是好习惯。
  • 命令别名(alias):直接输入alias可以查看当前可用的别名命令。通过如alias lm='ls -al'的命令则可以自己增加别名定义。
  • 作业控制、前台/后台控制(job control, foreground, background):比如作业控制,可以把工作放到后台进行,不用担心误操作ctrl+c中断了进程,还可以在单一登录环境下进行多任务。
  • 程序脚本(shell script):远比dos年代的“批处理文件”还强的功能,可以几乎是一个小的程序语言。
  • 通配符(Wildcard)的支持。
  • Enter的转义:输入一个反斜杠\然后按Enter,不会被当做执行的命令。反斜杠后紧接着的字符会被转义,这样可以换行继续敲命令。

脚本内变量

脚本内变量的命名

  1. 变量就是用特定的字符串代表不固定的内容,获得使用上的便利。特别是在在脚本程序(shell script)中会相当便利。
  2. 变量只能由英文字母和数字构成,而且开头不能是数字。
  3. 命名规则:通常的,全大写字符为系统默认变量 ;小写字符来作为自行设置的变量的名称。这不是强制的。
  4. 通过=可以简单的设置变量的值。等号两边不可以直接有空格符
  5. 取消变量,unset 变量名
  6. 小应用,如何进入自己的系统高度内核目录:cd /lib/modules/`uname -r`/kernel或者cd /lib/modules/$(uname -r)/kernel

在脚本内引用变量

  1. 引用的方式是`var`或者$(var)。$()与``作用一样,用于命令替换(command substitution)或变量替换,是用来重组命令行的——将其结果替换出来,再重组命令行。从方便看的角度说,其实后者比较好,从嵌套的角度来说也是,内部嵌套的时候,内层的反引号要用反斜杠来跳脱。
  2. 变量内容的累加,使用"$变量名"或者${变量名}来引用,比如:PAHT="$PATH":/home/bin。${}其实是变量替换,用于精确的界定变量名称的范围,一般来说,$${}没区别,都是引用变量,但是后者能界定范围,比如,可以用于2个变量的连接。

    1. 计算出变量值的长度:

      1
      ${#var}
    2. 数组操作:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      # 得到数组的全部元素
      ${A[@]}
      ${A[*]}
      # 得到数组的长度
      ${#A[@]}
      ${#A[*]}
      # 得到对应索引index的元素
      ${A[index]}
      # 得到对应索引index的元素的长度
      ${#A[index]}
  3. 变量内容里的空格符可以通过用双引号将整体内容包起来来保留,而特殊字符(包括$)在单引号内将变成普通的字符,比如var=“lang is $LANG”$LANG是变量,但是在var='lang is $LANG'里,$LANG就只是普通字符串。需要注意的是,单引号和双引号必须要成对。所以如果变量内容里有一个单引号,必须在外层补一个双引号。或者通过反斜杠转义。

  4. 转义符反斜杠\依然可以将特殊字符转义。

在脚本内检测变量

1
2
3
`${variable:-value}`   # 如果变量没有赋值或为空,使用value的值
`${variable:=value}` # 如果变量没有赋值或为空,使用value的值,并把变量赋值为value
`${variable:?}` # 检验变量是否为空

环境变量

  • 通过简单的env就可以查看环境变量了
  • 设为环境变量,使用export 变量名,这样该变量就可以在其他子进程中被使用了。这里所谓子进程,说的就是当前shell外新开的shell。或者说,在bash下所执行的任何命令都是由这个bash所衍生出来的,这些被执行的命名就被称为子进程。
  • set命令会将环境变量和bash内其他的变量全部都显现(bash操作接口和用户的自定义变量)。对bash的操作接口做一些改变可以改进bash的使用体验。
    1. PS1:这是提示符的设置,可通过修改这个变量的值,来改变shell命令行的提示符的内容。
    2. $:本shell的PID号,即线程代号(Process ID)。
    3. ?:上个执行命令的回传码。就像函数的return的结果。一般来说,如果成功执行了命令会返回一个0,反之则是“错误代码”。
    4. OSTYPE,HOSTTYPE,MACHTYPE:主机硬件和内核的等级。

变量I/O:键盘读取、数组与声明

  • read命令可以读取来自键盘输入的变量,经常用在shell script中,当执行到read [-pt] 变量名命令时,会停下来等用户输入。其中p后面可以带提示字符,t可以带等待的秒数。
  • declare/typeset声明变量的类型。单独使用declare,效果和set一致,会列出全部变量。之所以需要声明变量类型,是因为变量类型默认为“字符串”。通过命令declare [-aixr] variable

脚本在后台执行

  • 要让进程在后台运行,一般在命令后空格再加一个&即可。也就是:[shell order] &这样。
    1. 实际这样是把命令放入到一个作业队列中。
    2. 通过jobs -l可以查看任务,返回任务编号n和进程号。
    3. 可以将编号为n的任务转后台或者转前台来运行:bg %nfg %n(原先在前台的,则先ctrl + z暂停,然后再bg)
    4. 区别于ctrl + z挂起当前任务,ctrl + c是终止当前任务。
    5. 但是这样到后台执行的进程,其父进程依然是当前终端shell的进程,一旦父进程退出,就会发送hangup信号给它的所有子进程,子进程会在收到后退出。
  • 在退出shell继续运行进程
    • 可以增加前缀nohup来忽略hangup信号,也就是nohup [shell order] &
    • 可以setsid将父进程设为init进程(进程号为1)也就是setsid [shell order] &
  • 还可以让进程在一个subshell里执行,和setsid异曲同工。方法是将整个命令用圆括号包起来。
  • 使用screen,首先创建一个断开模式的虚拟终端,然后用-r选项重新连接这个虚拟终端,在其中执行的任何命令都是nohup的效果

脚本里的路径问题

pwd命令

  1. 这个命令其实是:“print name of current/working directory”。
  2. 如果你在脚本里调用这个,返回的是“执行命令的路径”而不是脚本文件所在路径!

在脚本里获得脚本自身所在的路径

  1. basepath=$(cd `dirname $0`; pwd -P)这样就可以把绝对路径存进变量bashpath里。
  2. 其中,是通过dirname $0获取到当前执行的脚本文件的父目录;cd是进入这个目录;pwd命令来获得绝对路径,pwd -P可以获得物理路径(非引用路径)
  3. 然后,需要用到的时候,比如要往一个文件里写入什么,就可以:echo "sth" >> `${basepath}/`test.config

$0变量是Bash环境下的特殊变量

  1. 真实含义:“ Expands to the name of the shell or shell script. This is set at shell initialization. If bash is invoked with a file of commands, $0 is set to the name of that file. If bash is started with the -c option, then $0 is set to the first argument after the string to be executed, if one is present. Otherwise, it is set to the file name used to invoke bash, as given by argument zero.”
  2. 根据调用方式的不同,其含义可能不同。
  3. 用一个文件调用bash,则是那个文件的名字(仅仅是名字)
  4. -c选项启动bash的话,真正执行的命令是从一个字符串里读取的,字符串后面如果有别的参数,则用从$0开始的特殊变量来引用。
  5. 其他情况下,$0会被设置成调用bash的那个文件的名字

使用Terminal的一些小经验

  • shift + pageup/pagedown可以对命令行窗口中的内容进行翻页。
  • 一些输出内容的命令可以通过> + 文件名 输出到对应的文件中并覆盖其内容;若使用>>则是追加其内容,比如常见的echo命令,还有比如配合tree命令将目录结构保存下来。
  • sh文件如果有x权限则可以直接通过文件名来运行,如果没有,就要通过sh 文件名来运行。通过ll命令可以查看文件权限
  • Backslash(\)的作用
    1. 起因,看到在rvm的安装说明里,命令是\curl -sSL https://get.rvm.io | bash -s stable
    2. 一开始以为开头这个Backslash是弄错了,但并不是,搜了一下stackoverflow
    3. 原文:“This is used to call the “original” command, avoiding it to be called with the possible aliases. That is, disables the possible aliases on the command curl and adjusts to the original one.”
    4. 因为shell是可以设置alias(别名)的,这个Backslash可以帮助你避免错误的使用了你之前设置的(很明显你的alias设置的并不好)别名。
  • 在shell脚本中,可以通过#!注释来指定当前脚本使用的解析器。