后浪笔记一零二四

A shell program, called a script, is an easy-to-use tool for building applications by “gluing together” system calls, tools, utilities, and compiled binaries.

Consider that as a Linux machine boots up, it executes the shell scripts in /etc/rc[0-6].d to restore the system configuration and set up services.

Shell scripting hearkens back to(追溯到) the classic UNIX philosophy(哲学) of breaking complex projects into simpler subtasks, of chaining together components and utilities.

1. token

token是构成源程序的基本不可再分割的单元。编译器编译源程序的第一步就是将源程序分割为一个个独立的token,这个过程就是词法分析。bash语言里面的token是怎么分割的呢?bash的token分隔符有两类:一类是操作符,还有一类自身没有特殊含义,仅用来分割其他token,被称为纯分隔符。

  • 操作符:操作符是一个天然的分隔符,同时其自身也是一个token
1
2
## =既是分隔符,也是token,所以这个简单的语句被分割为3个token:"a","=","myval"
a=myval
  • 纯分隔符:其本身不具备任何词法含义,只用来分割其他token。包括空格、制表符、换行符。 IFS: 由,,换行符三者之一组成。 IFS用来拆分命令行中的word用的,因为命令行是按word来处理的。(换行符不仅是纯分隔符,还是操作符)

  • Difference between \n(line feed,LF) and \r(carriage return,CR)? 故事开始于上古时期,那个时期的人们还在用打字机帮助自己不用笔。最早的时候打字机是纯机械不带电的,按下Enter是没有办法让光标挪动到下一行的,需要人们戳一下释放按钮把打字头弹回纸张最左边(CR),然后拉一下手柄让传动装置把纸张往上面挪一下(LF)。这就是CR永远在LF之前的原因

在Microsoft家族中,无论是CMD、powershell还是其他东东,它们都使用CRLF来当作“该使用下一行”的识别依据; 然而,Linux之类的Unix、类Unix系统则使用LF当作本行结束,而CR没有任何含义(除非在字符控制台)。

1
2
3
4
## 字符控制台
$ a=$(printf "hellooooo\r  again,\rgeorge\r\n")
$ echo "$a"
georgen,o

hellooooo again, george 所以打印genrgen,o(后面的会覆盖前面的)

  • Check whether a file uses CRLF or LF to end lines
1
2
3
4
5
6
7
8
9
## 1. 使用file命令查看
$ file README.md
README.md: UTF-8 Unicode text
## 2. 使用cat -A filename命令查看
Lines ending in CRLF will look like:
this is text^M$
## 3. 使用sed -n l filename命令查看
Lines ending in CRLF will look like:
this is text\r$

2. keyword和标识符

编程语言的标识符用来标志变量、类型、常量等语法对象的符号名称,其在语法分析时作为一个token存在。 编程语言中的标识符总体上分为两类:一类是语言设计者预留的标识符,一类是编程者可以自定义的标识符。 前者一般由语言设计者确定,包括语言的预声明关键字和用于后续语言扩展的保留字。 后者是用户在编程过程中自定义的变量名、常量名、函数名等一切符合语言规范的标识符。 用户自定义标识符不应该使用语言设计者的预留标识符,否则会造成编译器错误。

bash标识符的构成规则是:开头一个字符必须是字母或下划线,后面跟任意多个字符、数字或下划线。

bash预声明标识符包括:keyword,

① 关键字(使用type命令可以查看是否时keyword)

  1. function: 定义函数
  2. if-else-then:
  3. while-do-done:
  4. for-do-done:
  5. until-do-done:
  6. ! reverse(反转) (or negate) the sense of a test or exit status. The ! operator inverts(倒置) the exit status of the command to which it is applied. In yet another context, from the command line, the ! invokes the Bash history mechanism. Note that within a script, the history mechanism is disabled.
  7. $[[]]和[[]] [[]]比[]多了模式匹配的功能
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[[ n=1 ]]
echo $n
a=$[[ 1+3 ]]
echo $a

[[ $a == z* ]]    # True if $a starts with an "z" (pattern matching)
[[ $a == "z*" ]]  # True if $a is equal to z* (literal matching)
[[ $a =~ z* ]]    # True if $a starts with an "z" (pattern matching)
[[ $a =~ "z*" ]]  # True if $a is equal to z* (literal matching)

# [[]]可以用来比较8进制和10进制数
decimal=15
octal=017   # = 15 (decimal)

if [ "$decimal" -eq "$octal" ]
then
  echo "$decimal equals $octal"
else
  echo "$decimal is not equal to $octal"       # 15 is not equal to 017
fi

if [[ "$decimal" -eq "$octal" ]]
then
  echo "$decimal equals $octal"                # 15 equals 017
else
  echo "$decimal is not equal to $octal"
fi

② 内置数据类型标识符 字符串类型, 数字类型

③ 内置函数(具体见,04函数.md)

④ 常量值标识符 false, true

3. 字面常量

编程语言源程序中,表示固定值的符号叫做字面常量,简称字面量。 一般使用裸字符序列来表示不同类型的值。字面量可以被编程语言编译器直接转换为某个类型的值。 bash的字面量可以出现在两个地方:一是用于常量和变量的初始化,二是用在表达式里或作为函数调用实参。 定义变量和初始化变量的时候,不需要指定变量的类型,bash编译器会结合字面量的值自动进行类型推断。

bash只支持字符串和数字字面量。

4. 操作符(meta string)

bash的操作符非常多:

  1. $(()) 和 (()) 如果返回是0,(())就返回1状态码,否则返回0状态码。let命令也是这样。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ ((n = 1))
$ echo $n
$ a=$((1+3))
$ echo $a
## 使用三元运算符
(( var0 = var1<98?9:21 ))
## 逗号操作符可以连接多个算术运算,但是只有最后一个算术运算会返回运算的结果。
# Set "a = 9" and "t2 = 15 / 3"
let "t2 = ((a = 9, 15 / 3))"
# The comma operator can also concatenate strings.
for file in /{,usr/}bin/*calc
#             ^    Find all executable files ending in "calc"
#+                 in /bin and /usr/bin  directories
do
    if [ -x "$file" ]
    then
        echo $file
    fi
done

# /bin/ipcalc
# /usr/bin/kcalc
# /usr/bin/odicalc
# /usr/bin/oocalc

#将16进制数转换为10进制数
a=$((16#267a7901))
  1. $[]和[]
1
2
3
4
$ [ n=1 ]
$ echo $n
$ a=$[ 1+3 ]
$ echo $a
  1. 重定向输入和输出 date > test.txt #>会覆盖文件内容,>>不会覆盖文件内容: 输出重定向 wc < test.txt: 输入重定向 内联输入重定向(inline input redirection)。这种方法无需使用文件进行重定向,只需要在命令行中指定用于输入重定向的数据就可以了。 内联输入重定向符号是«,除了这个符号,你必须指定一个文本标记来划分输入数据的开始和结尾。任何字符串都可以作为文本标记,但在数据的开始和结尾文本标记必须一致。
1
2
3
4
wc << marker
I am laojia!
marker
1 3 13 # 依次为文本的行数,文本的词数,文本的字节数
  1. {}和() {} 表示命令列表: { pwd; ls; cd /etc; pwd; cd; pwd; ls; }

() 表示进程列表, 会生成一个子shell来执行对应的命令: (pwd; ls; cd /etc; pwd; cd; pwd; ls)

要想知道是否生成了子shell,需要使用echo $BASH_SUBSHELL命令。如果该命令返回0,就表明没有子shell。 如果返回1或者其他更大的数字,就表明存在子shell。可以在进程列表中使用嵌套括号来创建子shell的子shell。

1
2
3
4
5
6
$ (pwd; echo $BASH_SUBSHELL)
/home/hebt
1
$ (pwd; (echo $BASH_SUBSHELL))
/home/hebt
2

注意,在交互式的CLI shell会话中,子shell并不是真正的多进程处理,因为终端控制着子shell的I/O。 所以要实现真正的多进程处理,就不要使用交互式的shell,而是把命令写在文件中再执行。 或者将进程列表置入后台模式,这样既可以在子shell中进行繁重的处理工作,又不会让子 shell的I/O受限于终端。

  1. &,将进程置入后台 coproc和&都会进入后台模式,它们的区别是,coproc会生成一个子shell来执行命令,而&不会。 在后台模式中,进程运行时不会和终端会话上的STDIN,STDOUT以及STDERR关联。最好将后台运行的脚本的STDOUT和STDERR进行重定向,否则后台脚本的输出,输入的命令以及命令输出全都混在一起。
1
2
3
4
5
6
7
8
$ coproc sleep 10
[1] 30719
$ jobs
[1]+  Done                    coproc COPROC sleep 10 #COPROC是coproc命令给进程起的名字。
$ coproc My_Job { sleep 10; }                        #手动定义一个名字
[1] 30728
$ jobs
[1]+  Running                 coproc My_Job { sleep 10; } &
  1. :操作符(类似golang中的_操作符) This is the shell equivalent of a “NOP”(no op, a do-nothing operation). It may be considered a synonym(代名词) for the shell builtin true.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
## 1.提供一个占位符
: $((n = $n + 1))
## 等价于
((n = $n + 1))
## 作为if/then语句的占位符
if condition
then :  # Do nothing and branch ahead(向前的)
else
  take-some-action
fi
## truncates(截距) a file to zero length
: > data.txt
## 相当于
cat /dev/null > data.txt

## 2.相当于builtin true
:
echo $?  #0
## 在while循环中
while:
do
  operation-1
  ...
done
  1. 管道操作符 rpm命令通过Red Hat包管理系统(RPM)对系统上安装的软件包进行管理。配合-qa选项使用时,它会生成已安装包的列表,但这个列表并不会遵循某种特定的顺序。 rpm -qa | sort

  2. $符号列表(具体见,02变量和常量和类型系统.md)

    变量 描述
    * 含有所有命令行参数(以单个文本值的形式)
    @ 含有所有命令行参数(以多个文本值的形式)
    # 命令行参数数目
    ? 最近使用的前台进程的退出状态码
    - 当前命令行选项标记
    $ 当前shell的进程ID(PID)
    ! 最后执行的后台进程的PID
    0 命令行中使用的命令名称
    _ 前一条命令的最后一条输出,如果没有输出,就使用命令本身

$- prints The current set of options in your current shell. himBH means following options are enabled: H - histexpand “history expand” m - monitor h - hashall B - braceexpand “brace expand” i - interactive

常用的退出状态码:

状态码 描述
0 命令成功结束
1 一般性未知错误
2 不适合的shell命令
126 命令不可执行
127 没找到命令
128 无效的退出参数
128+x 与linux信号x相关的严重错误
130 通过Ctrl+C终止的命令
255 正常范围之外的退出状态码
默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出。你可以改变这种默认行为,返回自己的退出状态码。exit命令允许你在脚本结束时指定一个退出状态码。

退出状态码被缩减到了0~255的区间。shell通过模运算得到这个结果。一个值的模就是被除后的余数。最终的结果是指定的数值除以256后得到的余数。

  1. ‘和" shell命令行中的每个字符分为以下两种:
  • literal: 也就是普通纯文本,对shell来说没有特殊功能。
  • meta: 对shell来说,具有特定功能的标识符。 如果我们要在命令行中将这些meta字符的功能关闭的话,就要使用引号了:
  • 单引号: 凡在单引号中的所有meta均被关闭
  • 双引号: 在双引号中的大部分meta被关闭,但有些保留(如$,反引号,反斜线)
  • escape: 使用反斜线进行转义来关闭meta的功能。 引号的结合使用的是就近原则,永远记住,在shell中,引号永远只有一个作用,关闭meta字符。
1
2
echo google | grep 'go\{2\}gle'   # 这里的\是用来关闭grep命令的meta字符
echo google | grep -E 'go{2}gle'

结束命令,这就是按就执行命令的原因。

  1. ; 命令分隔符[semicolon]. 可以使用分号来分割多个命令。

  2. ;; case option的终止符

通配符(wildcard)

  1. {a..z} 打印a到z所有的字母。

  2. {} placeholder for text. Used after xargs -i (replace strings option). The {} double curly{弯曲} brackets are a placeholder for output text.

ls . | xargs -i -t cp ./{} $1
  1. {} ; pathname. Mostly used in find constructs. This is not a shell builtin Definition: A pathname is filename that includes the complete path. As an example, /home/bozo/Notes/Thursday/schedule.txt. This is sometimes referred to as the absolute path.

previous working directory. A cd - command changes to the previous working directory. This uses the $OLDPWD environmental variable.

  1. ~ home directory [tilde,波浪线]

  2. ~+ current working directory. This corresponds to the $PWD internal variable

  3. ~- previous working directory. This corresponds to the $OLDPWD internal variable.

  4. =~ regular expression match. This operator was introduced with version 3 of Bash

  5. 控制字符 change the behavior of the terminal or text display. A control character is a CONTROL+key combination (pressed simultaneously). A control character may also be written in octal or hexadecimal notation, following an escape.

Control characters are not normally useful inside a script:

  • Ctl-A Moves cursor to begginning of line of text(on the command-line)
  • Ctl-B Backspace(nondestructive)
  • Ctl-C Break. Terminate a foreground job.
  • Ctl-D Log out from a shell (similar to exit) EOF (end-of-file). This also terminates input from stdin. When typing text on the console or in an xterm window, Ctl-D erases the character under the cursor. When there are no characters present, Ctl-D logs out of the session, as expected. In an xterm window, this has the effect of closing the window.
  • Ctl-E Moves cursor to end of line of text (on the coommand-line).
  • Ctl-F Moves cursor of forward one character position (on the command-line).
  • Ctl-G BEL. On some old-time teletype terminals, this would actually ring a bell. In an xterm it might beep.
  • Ctl-H Rubout回扣(destructive破坏性的 backspace). Erases character the cursor backs over while backspacing.
  • Ctl-I Horizontal(水平) tab.
  • Ctl-J Newline (line feed). In a script, may also be expressed in octal notation – ‘\012’ or in hexadecimal – ‘\x0a’.
  • Ctl-K Vertical(垂直) tab. When typing text on the consule or in an xterm window, Ctl-K erases from the character under the cursor to end of line. Within a script, Ctl-K may behave differently, as in Lee Lee Maschmeyer’s example, below.
  • Ctl-L Formfeed(换页)(clear the terminal screen). In a terminal, this has the same effect as the clear command. When sent to a printer, a Ctl-L causes an advance(推进, advanced先进的) to end of the paper sheet.
  • Ctl-M Carriage(运输) return
  • Ctl-N Erases a line of text recalled from history buffer(on the command-line)
  • Ctl-o Issues a newline(on the command-line)
  • Ctl-P Recalls last command from history buffer(on the command-line)
  • Ctl-Q Resume(恢复) (XON) This resumes stdin in a terminal
  • Ctl-R Backwards search for text in history buffer (on the command-line)
  • Ctl-S Suspend(暂停) (XOFF) This freezes(冻结) stdin in a terminal. (Use Ctl-Q to restore input.)
  • Ctl-T Reverses(反转) the position of teh character the cursor is on with the previous character (on the command-lie)
  • Ctl-U Erase(抹去) a line of input, from the cursor backward to beginning of line. In some settings, Ctl-U erases the entire line of input, regardless of cursor position.
  • Ctl-V When inputting text, Ctl-V permits inserting control characters. For example, the following two are equivalent:
1
2
echo -e '\x0a'
echo <Ctl-V><Ctl-J>

Ctl-V is primarily useful from within a text editor.

  • Ctl-W When typing text on the console or in an xterm window, Ctl-W erases(抹去) from the character under the cursor backwards to the first instance of whitespace. In some settings, Ctl-W erases backwards to first non-alphanumeric character.

  • Ctl-X In certain word processing programs, Cuts highlighted text and copies to clipboard.

  • Ctl-Y Pastes back text previously erased (with Ctl-U or Ctl-W).

  • Ctl-Z Pauses a foreground job. Substitute operation in certain word processing applications. EOF(end-of-file) character in the MSDOS filesystem.

  1. $()和``(命令替换) 命令替换:从命令输出中提取信息,并将其赋给变量。 命令替换会创建一个子shell来运行对应的命令。子shell是由运行该脚本的shell所创建出来的一个单独的子shell。 正因为如此,由该子shell所执行的命令是无法使用脚本中所创建的局部变量的。

本文发表于 0001-01-01,最后修改于 0001-01-01。

本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。


上一篇 « 下一篇 »

赞赏支持

请我吃鸡腿 =^_^=

i ysf

云闪付

i wechat

微信

推荐阅读

Big Image