上一节讲了shel的循环语句,本节介绍shell的输入和输出。输入包括命令行的输入和键盘或文件的输入,输出主要讲解输出重定向。
1. shell的输入
1.1 读取命令行中参数
shell脚本通过美元符号读取命令行中的参数,比如$0表示程序名,$1表示第一个参数,$2表示第二个参数,${10}表示表示第10各参数,以此类推。建立脚本test34.sh如下:
#/bin/bash
total=$[ $1 * $2 + $3 ]
echo "$1 * $2 + $3 = $total"
运行如下:
./test34.sh 4 5 6
结果如下:
4 * 5 + 6 = 26
1)注意,当把字符串输入给shell脚本的时候,注意是以空格作为分隔符,如果字符串本身就有空格的话,那么用双引号或者单引号。举例脚本test35.sh如下:
#/bin/bash
echo "$1 is pretty nice!"
运行如下:
./test35.sh "wu lin wai zhuan"
结果如下:
wu lin wai zhuan is pretty nice!
2) 注意,$0是程序名,这个程序名是运行时输入的完整路径,而不是文件名。比如下面的test36.sh脚本:
#/bin/bash
echo "shell path is $0"
运行输入:
bash shell/test36.sh
结果如下:
shell path is shell/test36.sh
如果想要只截取出文件名的话可以使用basename命令,这个命令会返回不包含前面路径的脚本名。脚力脚本test37.sh
#/bin/bash
shellName=$(basename $0)
echo "shell path is $shellName"
运行输入:
bash shell/test37.sh
结果如下:
shell path is test37.sh
通过basename指令,我们可以通过脚本名来处理相应的逻辑,比如建立一个shell脚本,然后创建两个不同名字的链接,让这两个链接处理不同的逻辑。建立脚本test38.sh如下:
#/bin/bash
name=$(basename $0)
if [ $name = "add" ];then
res=$[ $1 + $2 ]
elif [ $name = "mul" ];then
res=$[ $1 * $2 ]
fi
echo "$0 -> result is $res"
然后,创建两个链接文件:
ln -s test38.sh add
ln -s test38.sh mul
输入:
./add 4 4
运行结果为:
./add -> result is 8
输入:
./mul 5 6
运行结果为:
./mul -> result is 30
1.2 常用的特殊参数
有时候需要知道命令行中所有参数的个数,可以使用$#;如果想知道最后一个命令行参数,使用${!#},如果没有参数的话,那么${!#}就是程序名;$*会把参数当做一个整体;$@会把参数也当做一个整体,不过可以用for指令一个个取出来,这样就可以遍历命令行参数。
建立脚本test39.sh如下:
#/bin/bash
echo "param num is $#"
echo "the last param is ${!#}"
for item in "$*";do
echo "item -> $item"
done
for item in "$@";do
echo "item -> $item"
done
运行如下:
./test39.sh wu lin wai zhuan
结果如下:
param num is 4
the last param is zhuan
item -> wu lin wai zhuan
item -> wu
item -> lin
item -> wai
item -> zhuan
1.3 使用shift移动参数
shfit指令会把命令行中的参数进行左移操作,把$3的值给$2,$2的值给$1,$1的值被丢弃。注意$1的值不会给$0。shfit后面可以加参数,表示左移几个参数,比如shift 2表示左移2
建立如下脚本:
#/bin/bash
while [ -n "$1" ];do
echo "param is $1"
shift
done
运行如下:
./test40.sh wu lin wai zhuan
结果如下:
param is wu
param is lin
param is wai
param is zhuan
1.2 读取用户的输入
除了在运行脚本的时候加入参数,还可以通过read命令来读取用户的输入。建立脚本如下:
#/bin/bash
echo "input the name"
read name
echo "name is $name"
运行脚本后并输入wu lin wai zhuan
结果如下:
name is wu lin wai zhuan
这个脚本用echo命令提示用户输入数据,并把输入读入了name这个变量。如果在read后面加上-p参数,也可以提示用户输入。建立脚本如下:
#/bin/bash
read -p "input the name:" name
echo "name is $name"
运行脚本后输入:wu lin wai zhuan
结果如下:
input the name:wu lin wai zhuan
name is wu lin wai zhuan
read命令会把输入进行划分,并把输入分配给单个变量,如果指定的变量个数不够的话,那么剩下的输入全部扔给最后一个变量。比如:
#/bin/bash
read -p "input name and age and other: " name age other
echo "name=$name,age=$age,other=$other"
运行脚本后输入如下数据并得到下面的结果:
input name and age and other: baizhantang 30 mo xiao bei
name=baizhantang,age=30,other=mo xiao bei
注意:如果不指定输入变量的话,输入的所有数据都会保留到REPLY这个环境变量中,比如
#/bin/bash
read -p "input name: "
echo "name is $REPLY"
运行脚本后输入数据得到结果如下:
input name: wu lin wai zhuan
name is wu lin wai zhuan
read命令可以指定超时时间,也就是说,用户如果在规定时间内没有输入的话,read命令就会产生一个非零返回值,比如:
#/bin/bash
if read -t 3 -p "input name: " name
then
echo "name is $name"
else
echo "input timeout"
fi
运行后不输入数据,结果为:
input name: input timeout
read命令可以使用-n参数指定用户输入的字符个数,当用户输入个数满足要求时会自动退出。比如
#/bin/bash
read -n1 -p "input answer:" answer
case $answer in
Y | y)
echo "answer is yes";;
N | n)
echo "answer is no"
exit;;
esac
运行脚本后,输入yes,,不过当输入y之后脚本立刻就往下执行了
input answer:yanswer is yes
之前的read命令会把用户的输入都显示出来,但是有些数据不希望在屏幕上显示的时候,可以增加-s参数,比如:
#/bin/bash
read -s -p "input password " password
echo "password is $password
运行后输入:123
结果为:
input password password is 123
1.3 从文件中读取
read命令可以从文件中而非用户从键盘中输入的数据,read命令每次从文件读取一行,读完之后会返回一个非0的返回值。比如,有一个文件testfiel,testfile的内容为:
au linwaizhuan
Aulinw ai zhuan
建立脚本如下:
#/bin/bash
cat testfile|while read line
do
echo "content is : $line"
done
运行后结果为:
content is : au linwaizhuan
content is : Aulinw ai zhuan
2. shell的输出
之前接触的shell输出是把内容打印到屏幕或者重定向到文件中,这次对其原理介绍一下。
2.1 标准文件描述符
linux系统中一切皆文件,通过文件描述符来表示一个文件。之前经常接触的有3个,分别是标准输入0,标准输出1,标准错误2。
2.1.1 标准输入
标准输入就是键盘的输入,不过也可以使用重定向用指定的文件来代替文件描述符。比如使用重定向输入到cat命令和sort命令
cat < testfile
sort < testfile
2.1.2 标准输出
标准输出就是输出到显示器,不过也可以通过重定向输出到文件,比如
date > testfile
date >> testfile
这两个都是重定向,在testfile文件中能看到两行date输出的结果。但是,如果命令返回码($?非零),这个命令的输出结果就不能重定向到相应的文件中,比如
abcdef >testfile
结果为:
abcf:未找到命令
并且testfile文件中为空,并没有“abcf:未找到命令”这句话,看着像是重定向到文件的操作失败了,这里需要了解标准错误。
2.1.3 标准错误
默认情况下,标准错误是输出到屏幕上的。如果想要把错误也重定位到文件中,可以在重定位符号前面加上2这个数字,比如:
acbdef 2> testfile
运行后屏幕上什么都不显示,testfile中内容为:
未找到 'abcdef' 命令,您要输入的是否是:
命令 'abcde' 来自于包 'abcde' (universe)
abcdef:未找到命令
箭头前面的2意思是把标准错误重定向到testfile中。
注意:如果夹杂着正确的命令和错误的命令,那么就需要把标准输出和标准错误都进行重定向,比如:
ll testfile abed 1> file1 2> file2
运行后,file1的内容为:
-rw-rw-rw- 1 aitian aitian 78 12月 22 22:54 testfile
file2的内容为:
ls: 无法访问'abed': 没有那个文件或目录
当然,如果想要把标准输出和标准错误都输出到同一个文件中,可以使用&> 进行重定向,比如
ll testfile abc &> file3
运行后,file3的内容为:
ls: 无法访问'abc': 没有那个文件或目录
-rw-rw-rw- 1 aitian aitian 78 12月 22 22:54 testfile
可以发现,错误日志被优先放到了前面。
2.2 shell中如何输出重定向
在脚本中有两个方法实现输出重定向,分别是临时重定向脚本中某一行输出,永久重定向脚本中的所有输出。
2.2.1 临时重定向
在脚本中如果想把某行输出重定向到对应的文件描述符,只要在重定向符号和文件描述符之间增加&即可,比如说标准错误,使用>&2。举例如下:
#/bin/bash
date >&2
echo "date is $(date)"
当不加重定位文件运行时,即:
./test49.sh
结果为:
2019年 12月 23日 星期一 22:55:34 CST
date is 2019年 12月 23日 星期一 22:55:34 CST
若以重定向运行,即./test49.sh >file
屏幕上显示:
2019年 12月 23日 星期一 22:56:18 CST
file的内容为:
date is 2019年 12月 23日 星期一 22:56:18 CST
2.2.1 永久重定向
临时重定向只能对加了&符号的语句生效,可以如果想要把脚本中的多个输出都重定向的话就需要每一个输出都加上,这比较麻烦。但是通过永久重定向可以解决这个问题。永久重定向通过exec命令。比如
#/bin/bash
exec 1>testfile
date
echo "wu lin wai zhuan"
date
运行后,打开testfile,内容为:
2019年 12月 24日 星期二 22:52:13 CST
wu lin wai zhuan
2019年 12月 24日 星期二 22:52:13 CST
上面的exec命令是把标准输出重定向到testfile中,并且对整个shell的输出生效。
注意上面的exec是启动了一个新进程并完成重定向。
有时候一个shell脚本中,想要向不同的文件重定向,或者不同的fd重定向到不同的文件,也可以使用exec做到。比如
#/bin/bash
exec 2>errfile
echo "normal line 1"
echo "normal line 2"
exec 1>normalfile
echo "normal line 3"
echo "error line 2"
运行后,屏幕输出:
normal line 1
normal line 2
normalfile内容为:
normal line 3
errfile内容为:
error line 2
2.3 shell中如何输入重定向
用exec同样可以实现输入重定向,让shell脚本从文件中去读取消息而不是从键盘输入。比如:
#/bin/bash
exec 0< testfile
while read line;do
echo "$(date) line content: $line"
done
运行后输出:
2019年 12月 25日 星期三 20:53:21 CST line content: 2019年 12月 24日 星期二 22:59:03 CST
2019年 12月 25日 星期三 20:53:21 CST line content: wu lin wai zhuan
2019年 12月 25日 星期三 20:53:21 CST line content: 2019年 12月 24日 星期二 22:59:03 CST
2.4 关闭文件描述符
#/bin/bash
exec 5> testfile
echo "fd 5 content" >&5
exec 5>&-
echo "fd 5 value" >&5
运行后,屏幕输出:
./test53.sh: 行 5: 5: 错误的文件描述符
testfile的内容为:
fd 5 content
3. lsof指令查看文件描述符相关操作
lsof命令能够查询文件描述符的相关信息,一般lsof -a后面加上-p或者-d的参数,-p表示指定进程的pid,后者表示指定文件描述符的编号。比如输入:
lsof -a -p $$ -d 2
输出为:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 8221 at 2u CHR 136,0 0t0 3 /dev/pts/0
其中,
COMMAND表示正在运行的可执行文件的名字的前9个字符;
PID为对应进程的pid
USER表示用户
FD表示描述符和访问类型,r表示读,w表示写,u表示读写
TYPE表示文件的类型,CHR代表字符型(标准输入,输出,错误都是这种类型),BLK表示块型,DIR表示目录,REG表示常规文件,REG最常见
DEVICE表示设备的设备号
SIZE表示文件的大小
NODE表示文件的节点号
NAME表示文件名
现在建立一个脚本:
#/bin/bash
exec 23> test23
exec 34< testfile
exec 5> test5
lsof -a -p $$ -d 0,1,2,23,34,5
运行后结果为:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 12205 aitian 0u CHR 136,0 0t0 3 /dev/pts/0
bash 12205 aitian 1u CHR 136,0 0t0 3 /dev/pts/0
bash 12205 aitian 2u CHR 136,0 0t0 3 /dev/pts/0
bash 12205 aitian 5w REG 8,21 0 538178 /home/aitian/at/shell/test5
bash 12205 aitian 23w REG 8,21 0 538174 /home/aitian/at/shell/test23
bash 12205 aitian 34r REG 8,21 13 537315 /home/aitian/at/shell/testfile
4. 特殊的/dev/null文件
任何内容输出到/dev/null文件都不会被保存,相当于它是一个自动清空的回收站。有些数据如果不想保存的话可以往这里面重定向。
比如:
date > /dev/null
运行后什么都不会显示。
再比如:
ll testfile abcde 2>/dev/null
输出:
-rw-rw-rw- 1 aitian aitian 13 12月 25 21:25 testfile
那段错误信息被放进了回收站了。
以后如果想要清空一个文件的话,可以输入:
cat /dev/null > testfile
5. tee命令实现既输出到屏幕又输出到文件
如果想要把输出的内容既显示到屏幕又要重定向到文件中,可以使用tee命令。比如
date | tee testfile
输出:
2019年 12月 25日 星期三 22:18:19 CST
并且testfile的内容也是这个。
tee命令默认是覆盖,如果想要追加的话,使用-a 选项。比如
date | tee testfile
输出:
2019年 12月 25日 星期三 22:23:14 CST
此时testfile的内容为:
2019年 12月 25日 星期三 22:18:19 CST
2019年 12月 25日 星期三 22:23:14 CST
6. 总结与展望
6.1总结
1)shel如何使用命令行的参数,比如$1为参数1,$#为参数个数,$*和$@表示所有的参数,但是$@可以用for循环一个个取出来;
2) 用shift命令左移参数,一次移动一个,shift n一次移动n个;
3) read命令读取用户的输入, read -p读取时候加提示语句,read -t3定时3s,read -n1读取到一个字符的时候马上退出;
4) 标准输出,标准输入,标准错误是个啥;
5)临时重定向:比如用>&2把标准输出临时重定向;
6)永久重定向:比如用exec 1>testfile命令;
7) 输入重定向和关闭文件描述符
8) lsof命令怎么用,常常加-a,-p,-d参数
9)/dev/null这个黑洞文件
10)tee命令输出屏幕和文件
6.2 展望
下次更新将学习使用shell脚本运行时的一些控制。
来源:CSDN
作者:A_T_
链接:https://blog.csdn.net/weixin_36888250/article/details/103706926