Bash tips

shell的内容平时多多少少会使用到,说道底是经验积累问题,虽然每次总能找到最后的答案,但是总不能每次一用到就一句一句去搜索吧,还是按照场景整理在一起比较方便,先整理在这,然后持续更新。

用于“生存”的基本命令

这一部分最开始是整理在旧blog中的,现在整合在这里,以后就在这里进行继续更新了。

变量定义相关的

export

export可以将临时定义的变量定义成环境变量,比如在一个shell中临时定义的一个变量就没法在新打开的那个shell中继续再使用。使用export之后,这个变量就变成了环境变量,就可以在子进程中(新开的shell貌似不是子进程)再进行使用。具体的Linux中环境变量的分类可以参考之前linux env一篇

env

env 本身也是一个设置环境变量的命令,但是这个环境变量的scope与export 不同,使用env的话,这里的scope仅仅限于命令行本身。比如这个env enable_mpi=ON enable_openmp=ON ./build_ascent.sh 在这个命令的情况下,定义的env varible仅仅在script里面有效script执行完成之后就没有效果了。在script中可以使用一下形式enable_cuda="${enable_cuda:=OFF}"调用环境变量并赋值。

echo

用于显示环境变量 echo $变量名 可以显示出具体的变 量来。

unset 变量名

这个用于取消刚才已经设置好的变量 unset之后 刚才已经定义好的变量就不在了

文件处理相关

cp

是在同一台linux上互相拷贝文件 而scp是在不同linux系统之间互相拷贝文件

scp

一种使用方式是将本地文件传输到远程服务器上,或者远程服务器上的文件传回到本地。认证时候的方式与ssh类似,参数格式是:

scp -i <私钥地址> 本地文件的路径 用户名@远程服务器的ip:远程服务器上的路径

另一种是进行端口映射,比如把本机上的一个端口映射到远程的服务器的某个服务上:

scp -p 4588 remote@www.abc.com:/usr/local/sin.sh /home/administrator
-v 用来显示具体的进度
-p 选择被占用的端口
-r 拷贝目录?

tar

可以用来压缩或者解压缩 具体的命令比较多 可以参考鸟叔p254
一般对tar.gz文件解压的时候 采用-xzvf参数 –x 表示使用解打包或解压缩的功能 –z表示通过gzip的方式进行解压 此时文件后缀最好是*.tar.gz –v表示在解压的过程中将处理的文件名显示出来 –f表示 filename后面接的是实际要进行处理的文件名
tar 用于打包的时候要这样使用
tar -czvf 打包之后所生成的文件名 需要打包的文件或目录
具体命令含义可以参考鸟叔p254
–z是打包成.tar.gz -j是打包成.tar.bz2

cp

复制文件或目录 cp [参数] 源文件 目标文件
重要参数 –a(相当于-pdr组合在了一起) 复制过去之后文件属性的参数也是一样的 默认情况 属性是不一样的

mv

移动文件或者重命名

rm

删除文件或目录
-f 强制删除 –r递归删除 –i产生交互的信息
注意删除文件的时候一定要谨慎使用-rf的参数

ssh

这个是使用security,shell远程登录其他的终端,关于ssh具体要了解的内容比较多,比如证书的原理以及地址,比如如何将本地端口映射到远程端口。具体可以参考之前一篇ssh tips。

sed

替换文件中的关键内容,常用的格式就是 sed -i 's/<old pattern>/<new pattern>/' <file name> This is really good when writting the scripts that test the program with different configuration files. 如果pattern中的内容是某些变量,这个时候可以用双引号来表示sed中 -i 之后的内容。

一般是用来替换一个配置文件中的某些内容
http://stackoverflow.com/questions/9366816/sed-unknown-option-to-s
注意:替换掉的字符串可能在其中含有”/“,这样在最后的串中就有多个“/”

on mac, the sed command is a little bit different, this is one example and both -i and -e parameter should be adopted.

sed -i '' -e 's/Uf01.bin/Uf02.bin/g' CombineAndVisVectors_02.pvsm 

查看磁盘的使用情况 处理空间不足的问题

df

查询目录的挂在情况 以及使用到的文件系统 以及基本的可用空间

du

查询到了哪个挂载的目录比较大的话,进入对应的那个挂载的目录,之后使用 du -ah -–max-depth=1 . 可以查询当前目录下每一个子目录的大小。

进程相关

ps

查看当前进程 具体参数较多 常用的有
ps –af查看全部的进程并且以全格式的方式显示出来

service start/status/restart

service –status-all 这个命令可以列出全部的可以用供求service来使用的脚本
service命令可以使用的启动脚本或者服务 都要是在/etc/init.d文件夹下已经存在的?

pgrep

pgrep 是通过程序的名字来查询进程的工具,一般是用来判断程序是否正在运行
-l 列出程序名和进程ID;
-o 进程起始的ID;
-n 进程终止的ID;

od命令

这个命令可以用来查看某个文件中的详细信息,主要是可以根据不同的进制将每个字节的信息显示出来,文件内容可以通过管道的方式传过来,也可以直接跟在后面用od打开。
注意几个具体的参数,w是表示每列可显示的字符数,d 表示十进制 o 表示八进制(系统默认值)x 表示十六进制 n表示不打印位移值

网络相关

查看端口的占用情况

lsof -i tcp:port

lsof可以列出系统当前某个端口所打开的文件

nc –zv hostip 80

这个命令可以检查以hostip主机的80端口 看是否这个端口已经被打开
比如 nc –zv localhost 80
这个可以查看主机的80端口是否正常被打开

netstat

也可以查看网络相关的状态信息

重启网络服务(ubuntu)

sudo /etc/init.d/networking restart
service network-manager restart

terminal相关的快捷键

ctrl+D 用户注销 并且按两下会关闭terminal

ctrl+alt 弹出新的terminal(in ubuntu)

ctrl+shift+T 在同一个大的Terminal窗口中生成新的小的窗口 这样切换比较方便 看起来比较好

shift+ctrl+v 将剪贴板中的内容粘贴到terminal中

linux 的terminal中比较通用的几个快捷键(经常用到的)

ctrl+A 跳转到整行的前面

ctrl+E 跳转到整个命令行的末尾

ctrl+W 删除下一个分隔符之前的字符串

Soft link

软连接可以将当前目录下的内容连接到一个其他目录下的可执行文件或者目录,这个极大地简化了脚本的书写。比如不同的experimental script 都要用到多个executatble file, 各种package在不同的dir之下,这时候在当前的目录下要用到多个 executable file 使用软连接就比较方便,具体命令也很简单 ln -s <src file/dir> <target link>. 这样不用进行explicit的copy就行讲各种目录或者文件放到同一个目录下,写script的时候就忽略了path的问题,也不用来来回回进行copy操作,删除的时候也很容易。

几个常用到的shell脚本

command substitution

具体可以参考这个. 总之就是介绍$()的使用,在具体执行的之后里面可以嵌套成实际的命令。 某个命令执行之后的结果变成一个变量,比如grep 或者 cut 之后的结果变成变量。

脚本写在一行

在使用docker或者使用k8s启动对应pod的时候,相应的启动参数通常都是一个string类型的数组。前两个参数通常是[“sh”,”-c”],第三个参数应该是所要执行的shell脚本,这个时候需要把平时看起来很长的脚本写到一行中。其实也很简单,只需要在换行的地方采用;隔开即可。具体的一些例子可以参考这个。

在k8s中注意args与command的使用与docker有区别,具体可以参考这个,仅仅通过docker方式启动的话,只需要使用args的参数而非command的参数。

判断某个文件/变量是否存在

-z可以判断这个变量的取值的长度是不是为0,如果存在,说明值的长度是不为0的。if [ ! -z “$JRE_HOME” ] 表示的是变量存在(内容长度不为0)。 类似的根据参数的不同可以判断文件夹(-d),文件(-f)是否存在。

#!/bin/bash
if [ ! -z "$JRE_HOME" ];then
JAVAFILE="$JRE_HOME"/bin/java
echo $JAVAFILE
echo "set JAVAFILE to JRE_HOME"
else
echo "not set JRE_HOME"
fi

For loop 循环遍历各种变量

这个在bench mark 的测试中使用的比较多,通常是先declare一个list然后再遍历这个list,比如下面这个例子:

STEP_LIST="10 20 30"
for STEP in ${STEP_LIST}
do
# do sth based on STEP
done

还有的就是遍历一个dir下面的所有文件,比如这样:

DIR=path_to_dir
for PLANFILE in "$DIR"/*
do
# do sth
done

遍历目录下所有的文件或者dir并执行一些操作

以下是一个具体的例子.
One good point is that, compared with using the regex to select specific key word, it might be convenient to use the cut or grep to select out the dedicated string.

#!/bin/bash

# get the current dir path and data dir path
HERE=`pwd`
DATADIR=/dir/to/data/path

# list data dir in the dedicated list
# assuming there are all kinds of dirs in the data dir
# otherwise, we need to use -d to decide if it is a dir
for d in $DATADIR/*/; do
#use basename to cut out the last part of the dir
baseDir="$(basename $d)"
#the dir name is sth like this syn.A.b64.n2.r64.G_p1_s500
#we choose the first part from it, the cut is always a good way
#to cut things if string is in a standard way such as using . to split all strings
DATASET="$(echo $baseDir | cut -d "." -f 1)"

#choosing the name of the dir file
#if it does not satisfy specific condition, then cotinue
#another trick is to use the grep, if [ -n "`echo $DATASET | grep syn`" ]
#can decide if the dedicated string is greped
if [ "$DATASET" != syn5 ]; then
continue
fi

# using cut to select another info from the string
# this parameter will be used in command script
NumRank="$(echo $baseDir | cut -d "." -f 5 |cut -c 2-5)"
# if it is less than 128
if [[ $NumRank -lt 128 ]]; then
echo $DATASET
currdir="$HERE/$baseDir"
mkdir -p $currdir
cd $currdir
# constructing commands used for executing
args="../parse_timetrace.py $NumRank 0 $d"

# if the dedicated output is exist, we do nothing
if [ -f gantt_worklet.png ]; then
echo "exist"
else
# otherwise, we echo the command line
# and execute associated python command based on it
echo $args
python3 $args
fi
cd ..
else
echo "larger than 128"
fi
done

关于while循环

直接参考这个(https://codingstandards.iteye.com/blog/780524)

常见的using case 是启动了一个程序之后 可能这个程序会创建一些shared file或者是shared dir 其他程序会读取这个shared file/dir的内容然后获取一些必要的信息,这个时候要确定这些文件已经存在了之后才能启动其他的程序。常用的方法是在shell脚本中设置一个while loop 不断地查看file或者dir,确定存在之后,再推出loop, 这里要注意的是,在while的command中,会使用空格的token来区分字符,但是在shell的赋值操作中,等于号要直接和变量连在一起

#check dir, if it is not exist, then create
if [ ! -d ./vtkdata ]; then
mkdir ./vtkdata;
fi
# check the dir, if it is not exist, then sleep and wait
while [ ! -d ./gs.bp ]
do
sleep 0.01
echo "dir not exist"
done
# check the file, if it is not exist, then sleep and wait
while [ ! -f ./<filename> ]
do
sleep 0.01
echo "file not exist"
done

让许多台机器使用ssh的方式进行通信

实际case是这样的,需要帮实验室的多台虚拟机搭建同样的测试环境,肯定是要脚本来执行了,先是在一台机器上把整个流程走通之后,再使用ssh的操作复制到其他的机器上来执行,按理说也不是很复杂的操作。

一般有两个脚本,第一个是使用ssh-key的方式,将本机生成的ssh-key的公钥放到其他机器上,ok之后,再将其他命令都用ssh的方式执行就不用用户名和密码了,用到了spawn与expect,整理类似的脚本还是比较折腾的。

#set -e
# the minion
IP_LIST="centosma1-12345-root@10.10.102.94 centosma2-12345-root@10.10.102.95 centosmi1-12345-root@10.10.102.96 centosmi2-12345-root@10.10.102.97 centosmi3-12345-root@10.10.102.98 centosmi4-12345-root@10.10.102.99"
IFACE=eth0
# check th id_rsa.pub , create the new id_rsa if not exist
ssh-keygen -t rsa
for IP in ${IP_LIST}
do
#split the str by -
U=$(echo ${IP} |cut -d '-' -f 1)
P=$(echo ${IP} |cut -d '-' -f 2)
SSHIP=$(echo ${IP} |cut -d '-' -f 3)
C=${U}
MA=$(echo ${SSHIP} |cut -d '@' -f 2)
/usr/bin/expect<<-EOF
set timeout 3
spawn /usr/bin/ssh-copy-id -i /home/wangzhe/.ssh/id_rsa.pub ${SSHIP}
expect {
"yes/no"{send "yes\r";exp_continue}
"password:"{send "12345\r";exp_continue}
}
EOF
#ssh ${SSHIP} mkdir -p /home/vcap/kubeindocker/
#mkdir -p ./deploylog
#ssh ${SSHIP} setsid sudo yum update && sudo yum install docker-engine 1>./deploylog/${MA}-stdout.log 2>./deploylog/${MA}-stderr.log
done
#set +x

这个脚本主要是中间那段,在/bin/bash 脚本中插入一段/usr/bin/expect 脚本来执行交互,这里要注意的是EOF的使用结尾的那个EOF要放到正行的开头这里坑了好久,最后还是在同学的帮助下完成的。还要注意下expect对于多种情况时候的处理,以及exp_continue的使用。

最后通过setsid方式后台运行命令,并且将标准输入和输出记录到log中。

在后台静默运行某个程序

主要的有三种方式,采用sesid、nohup以及&,可以参考这个(https://www.ibm.com/developerworks/cn/linux/l-cn-nohup/)

比如下面这个例子,启动etcd并且把对应的日志信息输出到指定的文件夹。

$ setsid ./etcd -name infra0 -data-dir infra0 -cert-file=server.crt -key-file=server.key -advertise-client-urls=https://127.0.0.1:2379 -listen-client-urls=https://127.0.0.1:2379 1>stdout.log 2>stderr.log
如果采用&的方式
$ ./etcd --listen-peer-urls http://127.0.0.1:2381 --addr=127.0.0.1:4001 --bind-addr=0.0.0.0:4001 --data-dir=/var/etcd/data &> ./etcd.log &

有一次采用&的方式静默启动,但是应用不定时地就自动关闭了,也没有什么特殊日志,总是就是很奇怪,还是采用service的方式比较好,实在不行也得用个docker吧,要不应用挂了也不知道,所谓的进程监控了。

关于linux中的输出重定向的问题可以参考这个(https://www.jb51.net/article/64183.htm)

关于输入输出的重定向 1 2 的写法:

https://superuser.com/questions/436586/why-redirect-output-to-21-and-12

关于IO buffer

直接运行程序的时候,printf的结果可以输出到terminal中,但有时候使用了重定向之后,结果就无法输出,除非等程序运行结束。具体比如参考这个或者参考这个 主要是因为IO buffer的原因导致结果不会直接输出

一种方式是使用 expect 工具的 unbuffer command 作为临时调试使用,这个时候不管是重定向还是使用pipeline都可以work。或者是在输出的时候使用std::endl强制进行flush。

另外一种是使用比较完备的log工具,会直接将log信息记录写在打开的文件中而并非是通过标准输出。事实上参考这里可以看到 line-buffer 的概念,什么时候flush是个问题,频繁的flush会造成performance issue,但是一个flush信息也没有又回造成难以交互,总之是个tradeoff,有趣的是使用std::endl代替\n结尾可以强制地进行flush。

通过grep命令判断是否截取出相关的内容

首先是grep的那一套操作,如果不需要grep的内容输出,可以将输出结果重定向到null中。
之后通过if_exist=$?返回上一条命令,也就是grep那个命令的状态码,之后再用一个if操作来判断状态码是0还是1,如果是1的话,说明grep操作的确截取到了相关的内容,如果为0的话,说明grep命令并没有截取到相关的内容。注意有的时候ps命令与grep结合,希望搜索出特定的进程信息,这个时候容易吧grep本身的进程也显示出来,此时可以使用-v参数,在这个搜索命令的最后部分加上|grep -v grep 把含义grep关键字的内容从搜索结果中去掉。如果是filter out 其他的关键字 可以用类似的方式 比如 | grep -v "total" 会filter out 含有 total 的那一行

It also seems that the -n parameter can be used together with the grep command to decide if the specific grep operation returns a valid string, checking this question for more details.

零碎的知识点

脚本中开头部分

一般比较常用的是写成 #!/bin/bash ,其中“#!”表示要位这个shell脚本指定一个解释器,当然后面还可以添加其他的内容,具体可以参考这个(https://blog.csdn.net/cjsycyl/article/details/7927727)

set使用

set -x 这个通常是于 set +x放在一起来使用的,开启调试模式。

set -x            # activate debugging from here
w
set +x # stop debugging from here

在”set -e”之后出现的代码,如果返回值是非0的,整个脚本就会立即退出,这个有时候会比较奇怪,有的时候可能脚本退出会引发一些奇怪的事情。一般命令正常就会返回0,命令异常就会返回其他的值。可以通过 echo $? 命令来查看上一条命令执行的状态码。

脚本中的空格问题

在给某个变量赋值的时候,等于号要和变量名紧挨在一起,不可以多一个空格出来,要不然系统会以为用户是在执行某个命令,这个实在是太容易错了,特别是对于像自己这样的新手而言。

单引号双引号反引号

网上相关的介绍内容较多。使用双引号,可引用除了字符$,, \ 外的任意字符或字符串。用单引号括起来的特殊字符串将没有意义,最常见的是,使用了 ‘$varname’ 之后,$varname就会以字面值的形式出现。可以在双引号字符串中嵌套单引号。shell会将反引号中的内容作为一个系统命令来输出。

寻找固定后缀的文件

找固定后缀的文件
find [path] -name .pyc
递归查找
find [path] -name ‘.pyc’

希望将json以格式化的方式输出

输出格式比较好的json信息,需要用到python的-m参数,加载一个库之后将内容输出
echo ‘{“foo”: “lorem”, “bar”: “ipsum”}’ | python -m json.tool

脚本中传入参数

关于脚本中参数的传递问题
脚本名称叫test.sh 入参三个: 1 2 3
运行test.sh 1 2 3后

$*为”1 2 3”(一起被引号包住)
$@为”1” “2” “3”(分别被包住)
$#为3(参数数量)

更多传入参数的处理方式可以参考这里。

Here Document
Here document(https://blog.csdn.net/wangjunjun2008/article/details/24351045)相关的内容,希望将一些内容写入到某个文件中,执行类似的操作时,可以使用重定向。

字符串的拼接

关于字符串的拼接,直接 $command=string1’xxxx’ 之后再echo $command就是可以的。
或者$command=${var1}${var2}这样也是可以拼接起来的。

命令替换与变量替换
区别命令替换与变量替换 $()这种方式与${}这种方式的区别。具体可以参考shell十三问之8。

变量分配时候的默认值
直接看别人整理的
http://unix.stackexchange.com/questions/122845/using-a-b-for-variable-assignment-in-scripts

其他参考资源

shell 十三问
http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=218853

这里找到了一个简体版
http://www.eefocus.com/haijiaoyouzi/blog/11-05/210795_9d1d9.html

这个分开整理的shell十三问,也比较好
https://blog.csdn.net/ancky_zhang/article/details/4583579

原帖
http://bbs.chinaunix.net/thread-218853-1-1.html

google的shell书写规范
https://zh-google-styleguide.readthedocs.io/en/latest/google-shell-styleguide/background/

bash编程基础总结
https://blog.csdn.net/marcky/article/details/7549513

推荐文章