Linux中特殊符号的作用

1 常见符号

1.1 $? $# $* $n $0 $@

  1. $?上一个命令的退出状态(若为0:表示成功;不是0,表示失败),或者上一个函数的返回值,但是有两个注意点:函数一结束就取返回值;退出状态码必须是0~255,否则就使用命令替换获取返回值或者定义一个变量(shell的文件级和函数中定义的变量默认都是全局变量)
  2. $#表示脚本中参数的个数
  3. $*表示获取所有对应参数的值
  4. $n表示为(n>=1)的参数
  5. $0表示脚本名
  6. $@表示获取所有对应参数的值
  7. $$ 表示Shell本身的PIDProcessID

$*$@区别:$*变量会将所有参数当成单个参数,而$@变量会单独处理每个参数

###这是测试脚本
#!/bin/sh
echo 这是脚本名字:$0
echo 总共有$#个人,分别是$*
echo 第一个人是$1,第二个人是$2
### 这是运行结果
这是脚本名字:sympol.sh
总共有2个人,分别是李四 张三
第一个人是李四,第二个人是张三

$?的测试:
在这里插入图片描述

1.2 分号

在脚本中,分号是多个语句之间的分隔符号,当只有一个语句的时候,末尾无需分号,最后一个语句后面也无需分号,否则报错
在交互式命令中,用分号隔开每个命令, 每个命令按照从左到右的顺序,顺序执行, 彼此之间不关心是否失败, 所有命令都会执行,例如:command1 ; command2
另外,在交互式命令中可以使用小括号包括起来:(command1 ; command2)这样就会建一个子shell,是多进程建造方法,也可以使用大括号,但是末尾需要加分号:{command1 ; command2 ; },但是这样创建的子shell会明显拖慢处理速度,在交互式的CLI shell会话中,子shell同样存在问题。它并非真正的多进程处理,因为终端控制着子shell的I/O

1.3 引号

单引号 中是原始字符串,属于强引用,它会忽略所有被引起来的字符的特殊处理,被引用起来的字符会被原封不动的使用,唯一需要注意的点是不允许引用自身

双引号 可以对特殊字符进行扩展, 属于弱引用,它会对一些被引起来的字符进行特殊处理。双引号与单引号的区别在于其可以包含特殊字符(单引号直接输出内部字符串,不解析特殊字符;双引号内则会解析特殊字符),包括', ", $, \,如果要忽略特殊字符,就可以利用\来转义,忽略特殊字符,作为普通字符输出

一般不写的就是当双引号用的

a=bcdef
echo $a # 输出bcdef
echo "$a" #双引号将进行变量扩展 ,输出bcdef
echo '$a' #单引号直接输出$a

点击此处了解shell语法中的空格和分号,引号

1.4 括号的作用

1.4.1 命令替换

shell脚本中最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。把输出赋给变量之后,就可以随意在脚本中使用了
两种方式:

  • 反引号字符(`)
  • $()格式

$( ) 和反引号` (tab按键上面) 作用相同:命令替换
在这里插入图片描述
命令替换还有一个作用就是可以用来获取函数返回值,可以避开使用$?0~255限制

1.4.2 命令列表和命令序列

Linux 中,命令列表使用小括号 (),而命令序列使用大括号 {}。这两种括号的使用有一些区别:

  • 命令列表(Command List)使用小括号 ()
    • 命令列表中的命令会在一个子shell中执行,这意味着它们在一个新的进程环境中运行。
    • 命令列表中的变量和环境设置不会影响当前 shell 环境。
    • 命令列表中的变量和环境设置不会传递给后续命令。
    • 命令列表中的命令按照顺序依次执行,无论前面的命令是否成功。
      例如:(command1; command2; command3)。
  • 命令序列(Command Sequence)使用大括号 {}
    • 命令序列中的命令在当前 shell 环境下执行,不会创建一个新的子shell
    • 命令序列中的变量和环境设置会影响当前 shell 环境。
    • 命令序列中的变量和环境设置会传递给后续命令。
    • 命令序列中的命令按照顺序依次执行,无论前面的命令是否成功。
      例如:{ command1; command2; command3; }。

需要注意的是,在命令列表和命令序列中,命令之间的分隔符可以是分号 ; 或换行符。使用分号时,多个命令可以写在一行上;使用换行符时,每个命令占一行

1.4.3 数组操作

1.4.3.1 定义

数组是存放相同类型数据的集合,在内存中开辟了连续的空间,通常配合循环使用
在这里插入图片描述
数组之间每个元素之间以空格间隔或制表符间隔,下标是从0开始从左往右依次增加

1.4.3.2 初始化数组

定义数组的方式:

  • 数组名=(1 2 3 4 5)
  • 数组名=([0]=1 [1]=2 [2]=3 [3]=4 [4]=5)
  • 列表名="1 2 3 4 5"
    数组名=($列表名)

数值类型的数组:一对小括号()表示数组,数组中元素之间使用空格来隔开,arr=(1 2 3 4 5)
字符串类型数组:同样,使用一对括号表示数组,其中数组中的元素使用双引号或者单引号包含,同样使用空格来隔开arr=('a' 'b' 'c')

1.4.3.3 输出数组

echo ${数组名[*]}*或@都是显示全部数组,@或是*,它扩展为数组的所有成员。这两种下标只有在双引号中才不同。在双引号中,${name[*]}扩展为一个词,由所有数组成员的值组成,${arr[@]}将数组的每个成员扩展为一个词。 如果数组没有成员,${arr[@]} 扩展为空串
echo ${数组名[下标]}:可以指定输出这一下标所对应的元素
echo ${#数组名[*]}或者 echo ${#数组名[@]}:可以查看数组的元素个数
echo ${!数组名[*]}或者 echo ${!数组名[@]}:可以查看数组所有元素下标,即:键名

数组的任何元素都可以用${arr[下标]}来引用,花括号是必须的,以避免和路径扩展冲突。

1.4.3.4 数组的操作

删除与添加:

  • unset 数组名[下标] :删除数组的某个元素
  • unset 数组名:删除数组
  • 数组名[下标]=需要追加的值
  • 数组名[数组长度]=需要追加的值
  • 数组名+=(“值1” “值2”)
  • 数组名=(“${数组名[@]}” “值1” “值2”)

数组切片与替换:

  • ${数组名[@]:下标:长度} ##数组切片,获取从数组的某个下标开始的多少个元素
    如果想要实现数组的切片可以使用 数组名=(${数组名[@]:下标:长度} )
    在这里插入图片描述
  • ${数组名[@]/旧字符/新字符}
    echo输出的结果不会影响源数组,想替换原数组需要 数组名=(${数组名[@]/旧字符/新字符})
    在这里插入图片描述

1.4.4 数值运算

  • $(( )) 是整数数值运算,也可用(( )) 代替
  • $[ ]也是进行数学运算的,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号($[ operation ])将数学表达式围起来
  • 乘号(*)前边必须加反斜杠(\)才能实现乘法运算

1.4.5 test运算

[ ]是代替test运算符的,方括号定义了测试条件。注意,第一个方括号之后和第二个方括号之前必须加上一个空格,否则就会报错。
test命令可以判断三类条件:

  • 数值比较
  • 字符串比较
  • 文件比较

(( )) :双括号命令允许你在比较过程中使用高级数学表达式。 test命令只能在比较中使用简单的算术操作
[[ ]]:双方括号命令提供了针对字符串比较的高级特性

1.4.6 ${}

1.4.6.1 界定符号

${} 界定符号,比如$ab,就相当于${ab},而${a}b才是只取a的值
对于linux符号的使用例子,可以参考本人的linux小游戏来熟悉

1.4.6.2 取路径,文件名,后缀
#假设一个变量名为file
file=/dir1/dir2/dir3/my.file.txt
${file#*/}:删掉第一个 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt
${file##*/}:删掉最后一个 / 及其左边的字符串:my.file.txt
${file#*.}:删掉第一个 . 及其左边的字符串:file.txt
${file##*.}:删掉最后一个 . 及其左边的字符串:txt
${file%/*}:删掉最后一个 / 及其右边的字符串:/dir1/dir2/dir3
${file%%/*}:删掉第一个 / 及其右边的字符串:(空值)
${file%.*}:删掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file
${file%%.*}:删掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my

记忆的方法为:

  • # 表示从左边算起第一个(键盘上#$ 的左边,要注意使用#时,删除符号*放在标志符号(/或.)左边)
  • ##:表示从左边算起最后一个
  • %表示从右边算起第一个(键盘上%$ 的右边,要注意使用%时,删除符号*放在标志符号(/或.)右边)
  • %%:表示从右边算起最后一个
  • 单一符号是最小匹配两个符号是最大匹配
  • *:表示要删除的内容,对于###的情况,它位于指定的字符(例子中的'/'和'.')的左边,表示删除指定字符及其左边的内容;对于%%%的情况,它位于指定的字符(例子中的'/'和'.')的右边,表示删除指定字符及其右边的内容。这里的*的位置不能互换,即不能把*号放在###的右边,反之亦然。

1.4.7 大括号扩展-批量操作

Bash大括号扩展,大括号包围的,用逗号隔开的参数会扩展为独立的多个参数,例如:touch a{1,3}.txt //只能创建a1.txta3.txt文件
使用两个点号..可以按顺序或规律执行,例如touch a{1..3}.txt //创建是a1.txt,a2.txt,a3.txt

1.4.7.1 批量创建文件

touch file:平时我们都是这样创建一个文件。
如果我们想创建的文件,它的名字都类似:file0.txt,file1.txt … … file9.txt,那我们可不可以用一个命令直接快速创建多个文件?
touch file{0..9}.txt:这条命令便可以实现上面的要求

1.4.7.2 批量删除文件

rm -rf file:删除一个文件。
如果我们想把上面批量创建的那些文件全部删除 该如何做呢?
rm -rf file{0..9}

1.4.7.3 批量创建文件夹

mkdir dir:创建一个文件夹。
如果我们想快速创建名字类似的文件夹该如何做呢? 同理,
mkdir mkdir{0..9}:这条命令便可以实现上面的要求。

1.4.7.4 批量删除文件夹

rmdir dir: 只可以删除一个空文件夹。
rm -rf dir:可以删除一个空、非空文件夹。
如果批量删除上面的生成的文件夹。同理,
rmdir dir{0..9} 或者rm -rf dir{0..9}

1.5 与(&)或(|)

1.5.1 与&

  • &:表示任务在后台执行,在后台运行,redis-server &
  • &&:表示前一条命令执行成功时,才执行后一条命令,如:echo '1' && echo '2'
  • >连用时,如:&>,命令生成的所有输出都会发送到同一位置,包括数据和错误或者使用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符,例如:exec 1>testout

1.5.2 或|

  • | :表示管道符,上一条命令的输出作为下一条命令参数
  • ||:表示上一条命令执行失败后,才执行下一条命令,如:cat nofile || echo '1'

2 脚本符号

2.1重定向输入输出

2.1.1 文件描述符理解

文件描述符缩 写描 述
0STDIN标准输入
1STDOUT标准输出
2STDERR标准错误

注意:在 重定向到 文件描述符时,必须在文件描述符数字之前加一个&,一般写法是2 >&1,把错误重定向到标准输出

echo "This is an error message" >&2

这行会在脚本的STDERR文件描述符所指向的位置显示文本,而不是通常的STDOUT

2.1.2 重定向符号理解

2.1.2.1 临时重定向
符号作用
命令<文件把文件作为命令的标准输入
命令<<分界符从标准输入中读入,直到遇到分界符停止
命令 > 文件把标准输出覆盖重定向到文件中
命令 >! 文件输出重定向,强制覆盖原来的文件
命令 >> 文件把标准输出追加重定向到文件中
命令 2> 文件把错误输出覆盖重定向到文件中
命令 2>> 文件把错误输出追加重定向到文件中
命令 >> 文件 2>&1
或者
命令 &> 文件
或者
命令 >& 文件
把标准输出和错误共同追加写入重定向到同一个输出文件

注意:&>bash shell提供了特殊的重定向符号,可以将STDERRSTDOUT的输出重定向到同一个输出文件
&>也可以写成>&,二者的意思完全相同,即:标准输出标准错误输出 都重定向到某一文件中
n >& m:将输出文件m 和n合并
n <& m:将输入文件m 和n合并

使用例子:

下面两种写法一样,都是从分界符EOF(分界符自己定义)读入后输出到file1
cat <<EOF >file1
cat >file1 <<EOF

注意:cat<<EOFcat<<-EOF区别
两者都是获取stdin,并在EOF处结束stdin,输出stdout
<<- : 分界符(EOF)所在航的开头部分的制表符都将被去除
例如:

cat > 1.txt<<EOF
TEST
EOF

以上写法是不会出错的,但是如果如下写就容易出错了

cat > 1.txt<<EOF
TEST
	EOF

EOF前面有制表符或者空格,那么EOF不会当作结束分界符,只会继续被当做stdin来输入,<<-就是为了解决这个问题的

cat > 1.txt<<-EOF
TEST
	EOF

如果像上面这么写就不会出错

注意cat name.csvcat < name.csv 区别
虽然cat < name.csv的运行结果与 cat name.csv 一样,但是它们的原理却完全不同。

  • cat name.csv:表示 cat 命令接收的输入是 name.csv文件名,那么要先打开这个文件,然后打印出文件内容。
  • cat < name.csv: 表示 cat 命令接收的输入直接是 name.csv 这个文件的内容, cat 命令只负责将其内容打印,打开文件并将文件内容传递给 cat 命令的工作则交给终端完成。
2.1.2.2 永久重定向

如果脚本中有大量数据需要重定向,那重定向每个echo语句就会很烦琐。取而代之,可以用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符

#!/bin/bash
# redirecting all output to a file
exec 1>testout
echo "This is a test of redirecting all output"
echo "from a script to another file."
echo "without having to redirect every individual line"

运行结果

$ ./test10
$ cat testout
This is a test of redirecting all output
from a script to another file.
without having to redirect every individual line

exec命令会启动一个新shell并将STDOUT文件描述符重定向到文件。脚本中发给STDOUT的所有输出会被重定向到文件
可以在脚本执行过程中重定向STDOUT

#!/bin/bash
# redirecting output to different locations
exec 2>testerror
echo "This is the start of the script"
echo "now redirecting all output to another location"
exec 1>testout
echo "This output should go to the testout file"
echo "but this should go to the testerror file" >&2

运行结果

$ ./test11
This is the start of the script
now redirecting all output to another location
$ cat testout
This output should go to the testout file
$ cat testerror
but this should go to the testerror file

可以使用与脚本中重定向STDOUTSTDERR相同的方法来将STDIN从键盘重定向到其他位置。 exec命令允许你将STDIN重定向到Linux系统上的文件中:exec 0< testfile这个命令会告诉shell它应该从文件testfile中获得输入,而不是STDIN。这个重定向只要在脚本需要输入时就会作用。下面是该用法的实例

#!/bin/bash
# redirecting file input
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done

运行结果

$ ./test12
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.

2.2 脚本调用

先来说一下脚本调用主要以下有几种方式:

  • fork: 如果脚本有执行权限的话,path/to/foo.sh。如果没有,sh path/to/foo.sh
  • exec: exec path/to/foo.sh
  • source: source path/to/foo.sh

三种方式对比:

Command解释
fork新开一个子 Shell 执行,子 Shell 可以从父 Shell 继承环境变量,但是子 Shell 中的环境变量不会带回给父 Shell
exec在同一个 Shell 内执行,但是父脚本中 exec 行之后的内容就不会再执行
source在同一个 Shell 中执行,在被调用的脚本中声明的变量和环境变量, 都可以在主脚本中进行获取和使用,相当于合并两个脚本在执行

2.2.1 fork

fork是最普通的, 就是直接在脚本里面用 path/to/foo.sh 来调用foo.sh 这个脚本,比如如果是 foo.sh 在当前目录下,就是 ./foo.sh。运行的时候 terminal 会新开一个子 Shell 执行脚本 foo.sh子 Shell 执行的时候, 父 Shell 还在。子 Shell 执行完毕后返回父 Shell子 Shell父 Shell继承环境变量,但是子 Shell 中的环境变量不会带回父 Shell

2.2.2 exec

execfork 不同,不需要新开一个子 Shell来执行被调用的脚本。 被调用的脚本与父脚本在同一个 Shell 内执行。但是使用 exec 调用一个新脚本以后, 父脚本中 exec 行之后的内容就不会再执行。这是 execsource 的区别

2.2.3 source

fork 的区别是不新开一个子 Shell 来执行被调用的脚本,而是在同一个 Shell 中执行。所以被调用的脚本中声明的变量和环境变量, 都可以在主脚本中进行获取和使用

2.2.4 实例操作

2.2.4.1 准备脚本

创建两个脚本
第一个脚本,我们命名为 exec.sh:

#!/bin/sh
A=1 
echo "before exec/source/fork: PID for exec.sh = $$" 
export A
echo "In exec.sh: variable A=$A"
 
case $1 in
        exec)
                echo -e "==> using exec…\n"
                exec ./test.sh ;;
        source)
                echo -e "==> using source…\n"
                . ./test.sh ;;
        *)
                echo -e "==> using fork by default…\n"
                ./test.sh ;;
esac
 
echo "after exec/source/fork: PID for exec.sh = $$"
echo -e "In exec.sh: variable A=$A\n"

第二个脚本,命名为 test.sh:

#!/bin/sh
echo "PID for test.sh = $$"
echo "In test.sh get variable A=$A from exec.sh"
 
A=2
export A

echo -e "In test.sh: variable A=$A\n"

这个例子是通过显示 PID 判断两个脚本是分开执行还是同一进程里执行,也就是是否有新开子 Shell。当执行完脚本 test.sh 后,脚本 exec.sh 后面的内容是否还执行。

2.2.4.2 结果分析

fork执行结果:

./exec.sh fork
before exec/source/fork: PID for exec.sh = 5374
In exec.sh: variable A=1
==> using fork by default…

PID for test.sh = 5375
In test.sh get variable A=1 from exec.sh
In test.sh: variable A=2

after exec/source/fork: PID for exec.sh = 5374
In exec.sh: variable A=1

fork 方式可以看出,两个脚本都执行了,运行顺序为1-2-1,从两者的PID值(exec.sh PID=5374, test.sh PID=5375),可以看出,两个脚本是分成两个进程运行的。

exec执行结果:

./exec.sh exec
before exec/source/fork: PID for exec.sh = 20224
In exec.sh: variable A=1
==> using exec…

PID for test.sh = 20224
In test.sh get variable A=1 from exec.sh
In test.sh: variable A=2

exec 方式运行的结果是,test.sh 执行完成后,不再回到 exec.sh。运行顺序为 1-2。从pid值看,两者是在同一进程 PID=20224中运行的。

source执行结果:

before exec/source/fork: PID for exec.sh = 25906
In exec.sh: variable A=1
==> using source…

PID for test.sh = 25906
In test.sh get variable A=1 from exec.sh
In test.sh: variable A=2

after exec/source/fork: PID for exec.sh = 25906
In exec.sh: variable A=2

source方式的结果是两者在同一进程里运行。该方式相当于把两个脚本先合并再运行。

2.3 脚本传参数

2.3.1 不指定参数名称

就像上面提到的特殊符号:${0},${1},${2}..
${0}获取到的是脚本路径以及脚本名,后面按顺序获取参数。${10}之前的都可以写为$1,$2

新建一个noparam.sh的文件

#!/bin/bash
echo "脚本${0}"
echo "第一个参数${1}"
echo "第二个参数${2}"

执行结果

./noparam.sh 1 4
脚本./noparam.sh
第一个参数1
第二个参数4

2.3.2 指定参数名称

指定参数名称主要用的是getopts,其中 $OPTARG 是一个特殊的变量,用于存储 getopts 命令解析命令行选项时的当前选项参数值。getopts命令用于解析shell脚本的命令行选项和参数,它可以处理短选项(以单个破折号开头的选项,如-a)和长选项(以两个破折号开头的选项,如--option)。

getopts变量通常在while循环中使用,与getopts命令一起处理命令行选项。当getopts解析到一个选项时,它将该选项的参数值存储在$OPTARG变量中。
下面是一个简单的示例,说明如何使用getopts$OPTARG处理命令行选项:

#!/bin/bash

while getopts ":a:b:" opt; do
  case $opt in
    a)
      arg_a="$OPTARG"
      ;;
    b)
      arg_b="$OPTARG"
      ;;
    \?)
      echo "Invalid option: -$OPTARG" >&2
      exit 1
      ;;
    :)
      echo "Option -$OPTARG requires an argument." >&2
      exit 1
      ;;
  esac
done

echo "arg_a: $arg_a"
echo "arg_b: $arg_b"
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值