一、shell简介

Shell是用户和Unix/Linux内核沟通的桥梁,用户的大部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。

它虽然不是Unix/Linux系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Unix/Linux系统的关键。

Shell 脚本: 其实就是命令的堆积。

Shell有两种执行命令的方式

  • 交互式(Interactive):解释执行用户的命令,用户输入一条命令,Shell就解释执行一条。

  • 批处理(Batch):用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。

shell只定义了一个非常简单的编程语言,所以,如果你的脚本程序复杂度较高,或者要操作的数据结构比较复杂,那么还是应该使用Python、Perl这样的脚本语言,或者是你本来就已经很擅长的高级语言。因为sh和bash在这方面很弱,比如说:

  • 它的函数只能返回字串,无法返回数组

  • 它不支持面向对象,你无法实现一些优雅的设计模式

  • 它是解释型的,一边解释一边执行,连PHP那种预编译都不是,如果你的脚本包含错误(例如调用了不存在的函数),只要没执行到这一行,就不会报错

二、第一个shell脚本

Shell脚本典型的开发周期:直接在命令行(command line)上测试。然后,一旦找到能够完成工作的适当语法,再将它们放进一个独立的脚本里,并为该脚本设置执行的权限。

1、编写

打开文本编辑器,新建一个文件,扩展名最好为sh(sh代表shell),虽然扩展名并不影响脚本执行,但是便于我们识别,而且对于编辑器来说,还可能进行语法高亮。

#!/bin/bash# print the "hello world"
echo "Hello World"

2、执行

运行Shell脚本有两种方法:

  • 作为可执行程序

chmod +x ./first.sh      # 使脚本具有执行权限
./first.sh               # 执行脚本

注意:一定是 ./first.sh。为什么呢?通常我们在执行命令的时候,shell会根据PATH变量定义的路径进行查找,而我们的当前路径一般不会加入PATH变量,所以就会提示 -bash: first.sh : command not found。

./first.sh  是通过相对路径,指明执行 当前目录下的叫做 first.sh 的文件。

  • 作为解释器参数

/bin/bash  first.sh

 3、运行原理

当shell执行一个程序时,会要求UNIX内核启动一个新的进程(process),以便在该进程里执行所指定的程序。内核知道如何为“编译型”程序做这件事。shell脚本并非编译型程序;当shell要求内核执行它时,内核将无法做这件事,并回应“not executable format file”错误信息。shell收到此错误信息时,就会认为“这不是编译型程序,那么一定是shell脚本”,”退回到shell”接着会启动一个新的/bin/sh 副本来执行该程序。

当系统只有一个shell时,“退回到/bin/sh”的机制非常方便。但现行的系统都会拥有好几个shell,因此需要通过一种方式,告知内核应该以哪个shell来执行所指定的shell脚本。事实上,这么做有助于执行机制的通用化,让用户得以直接引用任何的程序语言解释器,而非只是一个命令shell。方法是,通过脚本文件特殊的第一行来设置:在第一行的开头处使用 #!这两个字符(必须:顶行&&顶头)。

在计算机科学中,sha-bang是一个由井号和叹号构成的字符串行(#!),其出现在文本文件的第一行最前两个字符。 在文件中存在sha-bang的情况下,类Unix操作系统的程序载入器会分析sha-bang后的内容,将这些内容作为解释器指令,并调用该指令,并将载有sha-bang的文件路径作为该解释器的参数[1]。#! (magic number)为了让Linux内核识别这是什么格式的文件。

* * *

The sha-bang ( #!)  at the head of a script tells your system that this file is a set of commands to be fed to the command interpreter indicated. The #! is actually a two-byte magic number, a special marker that designates a file type, or in this case an executable shell script (type man magic for more details on this fascinating topic). Immediately following the sha-bang is a path name. This is the path to the program that interprets the commands in the script, whether it be a shell, a programming language, or a utility. This command interpreter then executes the commands in the script, starting at the top (the line following the sha-bang line), and ignoring comments.

#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/bin/awk -f

Each of the above script header lines calls a different command interpreter, be it /bin/sh, the default shell (bash in a Linux system) or otherwise. Using #!/bin/sh, the default Bourne shell in most commercial variants of UNIX, makes the script portable to non-Linux machines, though you sacrifice Bash-specific features. The script will, however, conform to the POSIX  sh standard.

Note that the path given at the "sha-bang" must be correct, otherwise an error message -- usually "Command not found." -- will be the only result of running the script.

#! can be omitted if the script consists only of a set of generic system commands, using no internal shell directives. The second example, above, requires the initial #!, since the variable assignment line, lines=50, uses a shell-specific construct. Note again that #!/bin/sh invokes the default shell interpreter, which defaults to /bin/bash on a Linux machine.

This tutorial encourages a modular approach to constructing a script. Make note of and collect "boilerplate" code snippets that might be useful in future scripts. Eventually you will build quite an extensive library of nifty routines. As an example, the following script prolog tests whether the script has been invoked with the correct number of parameters.

When you execute a program, the kernel checks whether it starts by some magic byte sequence. If the executable file starts with #!, the kernel interprets the rest of the line as an interpreter name.

If the executable file starts with \177ELF (where \177 is byte 127), it loads the file as an ELF executable; that's the normal kind on most unix systems nowadays.

If the kernel doesn't recognize the file format, it refuses to execute the file and returns the error ENOEXEC (Exec format error). When the shell notices that, it takes upon itself to execute the program as a shell script. If the magic line is not provided, a default shell is used to run the script. This default shell could either be Bourne shell (sh) which is the case in some flavors, however, in some other flavors, the default shell used is same as login shell to execute it. The thing is: Don't leave it to the system to decide the shell, always provide the shell which you want in the first line.

[root@skype ~]# file /etc/init.d/sshd
/etc/init.d/sshd: Bourne-Again shell script text executable
[root@skype ~]# file /bin/ls
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped

Q. Sha-Bang(#!)的编写有什么规范?

A. Sha-Bang(#!)应该位于脚本的第一行,并且顶格填写,否则都是错的,即使Sha-Bang之前的内容都是注释,这种错误是常见的,而且不易发现的,因为此时Sha-Bang(#!)所在行实际上是不起效的,系统使用了默认的命令行解释器

Q. 为什么推荐这种写法:#!/bin/env perl?

A. 因为这是有利于移植脚本到其它平台的写法,解释器的默认安装路径在各个操作系统是不太一样的,有的是/bin/,有的是/usr/bin/,甚至有可能是用户自定义的路径,使用env就基本上能够通用了。虽然env也有可能在/bin/或者/usr/bin/中,但通常的情况是在这两个路径下都有env,或者其中一个是另一个的符号链接

用户可以指定命令解释器,而非只是一个命令shell。通过脚本文件中特殊的第一行来设置:在第一行的开头处使用 #! 这两个字符。当一个文件的开头字符是 #! 时,内核会扫描该行的其余部分,看是否存在可用来执行程序的解释器的完整路径。(中间如果出现任何空白符号都会略过),此外,内核还会扫描是否有一个选项(有且只能有一个)要传递给解释器,内核会以被指定的选项来引用解释器。

#! /bin/csh –f

#! /bin/awk –f (直接通过内核调用awk程序解释后面的脚本,而不是shell间接调用)

假设有一个awk脚本,/usr/test.awk,它的第一行如下: #! /bin/awk –f,如果shell的查找路径(PATH)中有 /usr,当用户键入 test.awk时,内核解释 #! 这行后,便会以如下的方式来引用awk:

/bin/awk–f  /usr/test.awk

这样的机制让我们得以轻松地引用任何的解释器。

4、注释

以”#“开头的行都是注释,会被解释器忽略。

5、输出

echo命令将参数打印到标准输出(把字符串转换为数据流),参数之间以一个空格隔开,并以换行符号结尾。-n选项会省略结尾的换行符。` ` 或者 $()命令则把数据流转换为字符串。

printf 命令模仿C程序库里的printf()库函数。它几乎复制了其所有功能。完整的语法分为两部分: printf  “format-string”  [argumnets …]

参数以空白符分隔,如果参数的个数比格式声明的多,则printf会循环且依次的使用格式字符串里的格式声明,直到处理完参数。意味着把参数依次左移处理。这是和C printf函数最大的不同。

三、条件判断

    很多时候,我们都需要进行条件判断,然后对不同的结果产生不同的行为。比如判断 3 是否 大于 2,我们可能想当然像下面这样,在命令行上:

[root@skype tmp]# 3 > 2
-bash: 3: command not found
[root@skype tmp]# ls
2

很遗憾,在命令行上,不能直接这样测试,因为对于 shell 来说, > , < 都是元(meta)字符,具有特殊含义(重定向)。

为了解决这种尴尬,于是shell专门提供了关于测试的命令: test, [ , ` `, 可以针对 数字、字符串、文件进行测试。可以man bash得到更多的信息,在里面找到对”CONDITIONAL EXPRESSIONS”的描述。

test 与 [  同样用于条件测试: "["是一个可执行程序,路径是"/usr/bin/[", [ 是一个命令,它是内置命令test的简写形式,只不过它要求最后一个参数必须是 ]。在使用[ ] 进行判定的时候有一个事项要注意的是,在括号两边以及符号两边均要有空格。

运算符            描述              示例
## 文件状态测试
-e filename     如果filename 存在,则为真       [ -e /var/log/syslog ]
-d filename     如果filename 为目录,则为真      [ -d /tmp/mydir ]
-f filename     如果filename 为常规文件,则为真    [ -f /usr/bin/grep ]
-L filename     如果filename 为符号链接,则为真    [ -L /usr/bin/grep ]
-r filename     如果filename 可读,则为真       [ -r /var/log/syslog ]
-w filename     如果filename 可写,则为真       [ -w /var/mytmp.txt ]
-x filename     如果filename 可执行,则为真      [ -L /usr/bin/grep ]
filename1 -nt filename2  如果 filename1 比 filename2 新,则为真 [ /tmp/install/etc/services-nt /etc/services ]
filename1 -otfilename2   如果filename1 比filename2 旧,则为真  [/boot/bzImage -ot arch/i386/boot/bzImage ]## 字符串测试 (请注意引号的使用,这是防止空格扰乱代码的好方法)
-z string        如果 string 长度为零,则为真     [ -z $myvar ]
-n string        如果 string 长度非零,则为真     [-n $myvar ]
string1 = string2   如果 string1 与 string2 相同,则为真 [$myvar = one two three ]
string1 != string2   如果 string1 与 string2 不同,则为真    [$myvar != one two three ]
string1 < string2       如果string1在本地的字典序列中排在string2之前,则为真     [[$myvar < "one" ]]
string1 > string2       如果string1在本地的字典序列中排在string2之后,则为真     [[$myvar > "one" ]]## 算术测试
num1 -eq num2             等于       [ 3 -eq $mynum ]
num1 -ne num2             不等于      [ 3 -ne $mynum ]
num1 -lt num2             小于       [ 3 -lt $mynum ]
num1 -le num2             小于或等于    [ 3 -le $mynum ]
num1 -gt num2             大于       [ 3 -gt $mynum ]
num1 -ge num2             大于或等于    [ 3 -ge $mynum ]

bash [ ] 单双括号

基本要素:

[ ] 两个符号左右都要有空格分隔

内部操作符与操作变量之间要有空格:如  [  “a”  =  “b”  ]

字符串比较中,> < 需要写成\> \< 进行转义

[ ] 中字符串或者${}变量尽量使用"" 双引号扩住,避免值未定义引用而出错的好办法

[ ] 中可以使用 –a –o 进行逻辑运算

[ ] 是bash 内置命令:[ is a shell builtin

bash  [[  ]] 双方括号

基本要素:

` ` 两个符号左右都要有空格分隔

内部操作符与操作变量之间要有空格:如  [[  “a” =  “b”  ]]

字符串比较中,可以直接使用 > < 无需转义

` ` 中字符串或者${}变量尽量如未使用"" 双引号扩住的话,会进行模式和元字符匹配

[[] ] 内部可以使用 &&  || 进行逻辑运算

` ` 是bash  keyword:[[ is a shell keyword

` ` 其他用法都和[ ] 一样


[[  ]] 比[ ] 具备的优势

①[[是 bash 程序语言的关键字。并不是一个命令,` ` 结构比[ ]结构更加通用。在[[和]]之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换。

②支持字符串的模式匹配,使用=~操作符时甚至支持shell的正则表达式。字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串,比如[[ hello == hell? ]],结果为真。` ` 中匹配字符串或通配符,不需要引号。

③使用` `.``.``.` `条件判断结构,而不是[... ],能够防止脚本中的许多逻辑错误。比如,&&、||、<和> 操作符能够正常存在于` `条件判断结构中,但是如果出现在[ ]结构中的话,会报错。

④bash把双中括号中的表达式看作一个单独的元素,并返回一个退出状态码。

使用` `.``.``.` `条件判断结构, 而不是[ ... ], 能够防止脚本中的许多逻辑错误. 比如,&&, ||, <, 和> 操作符能够正常存在于[[]]条件判断结构中, 但是如果出现在[ ]结构中的话, 会报错。

注意事项:

因为Bash变量不是强类型的,所以你应该小心:

#!/bin/basha=4
b=5#  Here "a" and "b" can be treated either as integers or strings.
#  There is some blurring between the arithmetic and string comparisons,
#+ since Bash variables are not strongly typed.#  Bash permits integer operations and comparisons on variables
#+ whose value consists of all-integer characters.
#  Caution advised, however.echoif [ "$a" -ne "$b" ]
thenecho "$a is not equal to $b"echo "(arithmetic comparison)"
fiechoif [ "$a" != "$b" ]
thenecho "$a is not equal to $b."echo "(string comparison)"#     "4"  != "5"# ASCII 52 != ASCII 53
fi# In this particular instance, both "-ne" and "!=" work.echoexit 0

As S.C. points out, in a compound test, even quoting the string variable might not suffice. [ -n "$string" -o "$a" = "$b" ] may cause an error with some versions of Bash if $string is empty. The safe way is to append an extra character to possibly empty variables, [ "x$string" != x -o "x$a" = "x$b" ] (the "x's" cancel out).


    各位想过一个问题没有,为什么能够进行测试呢?是根据什么来进行判断的呢?比如 if 语句:

当我们执行命令后,通常会返回2类值:状态返回码(exit status),以及命令执行返回结果:标准输出或标准错误输出。测试命令是根据  exit status 进行判断的,和 标准输出的信息没有半毛钱关系。

写了一个很2B的程序:

结果无论怎么执行,都是执行的 else 部分。单独执行每个命令,如下

修改如下即可:

所以:Shell脚本典型的开发周期:直接在命令行(command line)上测试。然后,一旦找到能够完成工作的适当语法,再将它们放进一个独立的脚本里,并为该脚本设置执行的权限。

四、算术运算

大家可以参考:http://mingxinglai.com/cn/2013/01/different-ways-of-doing-arithmetic-operators-in-linux/

6种算术运算方法是:

  1. let operation

  2. expr operation

  3. $[ operation ]

  4. $(( operation ))

  5. 用 awk 做算术运算(算术扩展)

  6. echo "operation" | bc

前面4种shell 脚本进行算术运算的方法, $(()) 最为常用,可以像 C语言 一样流畅的写表达式,所有变量可以不加入:“$”符号前缀。(()) 进行运算, $(()) 取出运算结果。有了它,我们就可以抛弃: let, expr 命令了。

a=$((a+1, b++, --c));
echo $a,$b,$c

但是它们都有一个致命的缺陷,都不支持浮点数。Bash仅支持整数运算(直接把小数部分截断(Truncate))。

[root@skype ~]# echo $((10 / 3))
3
[root@skype ~]# echo $((10.3 / 3))
-bash: 10.3 / 3: syntax error: invalid arithmetic operator (error token is ".3 / 3")

这里有一个奇怪的现象:

[root@skype ~]# echo $((10.3 / 3)) &> /dev/null
-bash: 10.3 / 3: syntax error: invalid arithmetic operator (error token is ".3 / 3")

为什么无法进行重定向呢? 因为重定向是 shell把其他进程的输入输出进行重定向。 而这个错误是 bash本身的致命错误,所以当然无法重定向咯。

扯远了,那么此时我们可以通过 bc, awk 来进行更复杂的运算或浮点运算。

bc 交互模式:

[root@skype ~]# bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
10 / 3            # 未指定精度默认保留整数
3
scale = 6         # 指定精度
10 / 3
3.333333
quit              # 退出交互模式
[root@skype ~]#

bc非交互式:

直接把算术表达式送给bc即可,如果要指定精度,加上scale=N; 用分号隔开

# +, -, *, / , ^
[root@skype ~]# echo 'scale = 2; 9 + 8 * 2 - 6 / 5 + 2^3' | bc
31.80# functions
[root@skype ~]# echo 'scale = 2; sqrt(15)' | bc
3.87# 进制转换
[root@skype ~]# echo 'ibase=16; obase = 2; 8' | bc
1000

五、流程控制

=====
for var in item1 item2 ... itemN
docommand1command2...commandN
done
# 把 var分别赋值, var=item1, var=item2, ...var=itemN=====
for file in *
#           ^  Bash performs filename expansion
#+             on expressions that globbing recognizes.=====
# Missing in [list] in a for loop
for a
doecho -n "$a "
done#  The 'in list' missing, therefore the loop operates on '$@'
#+ (command-line argument list, including whitespace).======
for (( EXP1; EXP2; EXP3 ))
docommand1command2command3
done=====
while condition
docommand
done=====
until condition
docommand
done========
case "$variable" in "$condition1") command... ;; "$condition2") command... ;; esac# 仅仅是匹配,而没有赋值操作# 支持文件名 通配
"E" | "e" )* )
[[:upper:]]   ) echo "Uppercase letter";;
[0-9]         ) echo "Digit";;
[a-zA-Z]*) return $SUCCESS;;  # Begins with a letter?


转载于:https://blog.51cto.com/skypegnu1/1624464

Linux基础:Shell脚本学习相关推荐

  1. 一本不错的Linux/Unix Shell脚本学习教程

    找到一本很详细的Linux Shell脚本教程,其实里面不光讲了Shell脚本编程,还介绍了系统的各种命令 http://www.dingbing.com/book/linuxshell.zip 点这 ...

  2. Linux基础-shell脚本编程

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者丨土豆居士 来源丨一口Linux shell 编程 一.变量 1. shell脚本基础知识 编译型 ...

  3. Linux基础——”shell脚本编程“ 你知道自动轰炸脚本怎么来的吗?

    文章目录 shell 编程 一.变量 1. shell脚本基础知识 2.shell 编程的基本过程 3. shell变量 二.shell 功能语句 常用功能性语句(命令) 测试语句 三.shell 分 ...

  4. linux之shell脚本学习篇一

    为什么80%的码农都做不了架构师?>>>    此文包含脚本服务请求,字符串截取,文件读写内容,打印内容换行. #!/bin/bash retMsg=""; wh ...

  5. linux shell脚本学习

    linux shell脚本学习笔记 文章目录 linux shell脚本学习笔记 一.脚本入门 1.我的第一个linux脚本 2.关于date命令的知识 二.shell脚本中的变量 1.为什么脚本需要 ...

  6. linux之基础shell脚本编程1 基础变量赋值

    本章主要介绍函数的基础,变量赋值,逻辑运算,条件表达式,测试 linux之基础shell脚本编程1  基础变量赋值 linux之基础shell脚本编程2 if语句循环判断 linux之基础shell脚 ...

  7. linux shell脚本攻略_(python)Linux下shell脚本监控Tomcat的状态并实现自动启动步骤...

    今天为大家带来的内容是:(python)Linux下shell脚本监控Tomcat的状态并实现自动启动步骤 本文内容主要介绍了Linux下shell脚本监控Tomcat的状态并实现自动启动的步骤,文章 ...

  8. 2021-04-09 linux的shell脚本简单教程

    linux 的基本操作(编写shell 脚本) 如果文章中的图片再次挂掉了,麻烦请去公众号内查看  终于到shell 脚本这章了,在以前笔者卖了好多关子说shell脚本怎么怎么重要,确实shell脚本 ...

  9. 【Linux】shell脚本实战-if多分支条件语句详解

    文章目录 前言 多分支语句的语法 多分支语句举例: 1. 出嫁的条件 2. 管理http服务实战脚本 3. 猜数字游戏v1版本-if版本 总结 前言 前面我们已经学习过单分支语句和双分支语句的使用. ...

  10. Linux基础shell编程-琐碎知识点

    shell 脚本学习-网络野路子 shell脚本攻略 Linux命令大全(手册)_Linux常用命令行实例详解_Linux命令学习手册 Unix 基础知识 shell知识点_亦乐-可乐的博客-CSDN ...

最新文章

  1. 阿里云的java规范_阿里JAVA开发强制要求的15条并发处理规范,切记
  2. 【PC工具】图片压缩哪家强!tinyPNG图片压缩工具
  3. 雌性激素过高怎么办?
  4. matlab 角域重采样,matlab滤波技术与区域处理---区域滤波
  5. 怎么用vnc访问自己内网电脑,同时又是同一个会话?
  6. Eclipse + Pydev开发Python时import报错解决方法
  7. hbase启动的时候报错java.lang.ClassNotFoundException: org.slf4j.LoggerFactory
  8. git常用命令之log
  9. linux suid 脚本,Linux使用suid vim.basic文件实现提权
  10. 无法从服务器同步注册表数据,辅助域控和主域控无法同步?!!
  11. 【数据结构笔记35】C实现:有序子列的归并算法:递归与非递归的实现
  12. 02 华为交换机配置telnet远程登录
  13. 【图像增强】基于matlab直方图均衡化图像增强【含Matlab源码 960期】
  14. 使用LordPE和Import REC脱壳
  15. 中国DNA测序在药物发现市场中的应用市场深度研究分析报告
  16. 线性查找python_C3-Linearization--线性化python
  17. 【MM模块】Physical Inventory  库存盘点流程
  18. 如何用 Python 让你的 PPT 数据动起来?
  19. html特效代码 枫叶,jQuery飘落的枫叶
  20. 国产单通道直流有刷马达驱动芯片型号推荐

热门文章

  1. 大话存储pdf 百度网盘_学用系列亲身体验百度网盘内测在线文档,有遗憾也有期待...
  2. python解析数据包时出现问题_MySQL Connector / Python InterfaceError:“解析EOF数据包失败”...
  3. nginx php unix负载,使用nginx配置多个php fastcgi负载均衡
  4. python36中文手册_python_36_文件操作4
  5. php js 异步上传图片,javascript实现异步图片上传方法实例
  6. 一周一论文(翻译)——[SIGMOD 2015] Congestion Control for Large-Scale RDMA
  7. stm32f746 linux,在Linux系统下搭建STM32开发环境--Nucleo-F429ZI
  8. c#语言中代替指针,如何在C#中使指针通用?
  9. VPS批量管理软件--远程桌面批量管理
  10. print输出字体特效