【脚本】shell指南

REPL解析过程

当你在命令行中输入字符时,比如最简单的,

$ ls

bash会查找ls然后执行

但是,如果你输入任何允许的东西,bash怎么处理,比如:

$ cmd=ls
$ $cmd

bash会如何解释$cmd呢?这里,变量替换是生效的,因此bash执行ls。
如果cmd中包含多个参数呢?

$ cmd="ls -l"
$ $cmd

bash会使用IFS来分割$cmd,因为它没有使用双引号包含,所以变量会展开之后分割,考虑:

$ cmd="ls,-l"
$ IFS=,
$ $cmd

如果你unset IFS,则$cmd会报错找不到"ls,-l"。

除了变量替换之外,命令的解析支持$’\x10’语法吗?答案是支持。

# 等价于echo yes
$ $'\x65ch\x6f' yes
yes

eval

eval会将目标参数使用空格join成字符串,然后执行

IFS

当bash遇到一个字符串序列时,如果它看到这个字符串没有使用引号,就会通过IFS将这个字符串展开成多个域。IFS影响的是字符串的展开,而不会影响脚本的命令按空格分隔的原则。

注意,如下的命令不会生效

a='echo,-n,yes'
IFS=, $a

并不会执行echo -n yes, 因为$a的解析是在IFS=,这一行语句之前,IFS只是影响导出的环境变量。

交互

特殊字符

Special Chars - tldp

Bash core

bultin-in

bultin-in - tldp

Subshell和子进程的区别

subshell - tldp
当使用()运行子命令时,子命令是在subshell中执行的,subshell的赋值不会影响父shell,但父shell的未导出的变量,函数,数组等在subshell中可读。

$$ 在子shell和父shell中都是一致的
$BASHPID 则是每个shell自有的

C-Style

在bash中某些地方可以使用c语言风格的语法

{a,b}展开

{xxx,yyy,zzz,…}其中{}是展开符号,如

echo {a,b}{c,d}
--> ac ad bc bd

{} 匿名函数

{}创建一个匿名函数,但是不能使用local

[], [[]],和test

注意,[和test是同义词,都是shell的内置命令,但是[[是关键字。

$[ … ]

正数展开,和$((…))一样

&>

将stdout和stderr都重置

(echo yes;echo no >&2) &>/dev/null

退出值

let和(())的退出值是根据结果是否是0来判断的, 为0时返回1(错误),非0返回0(正确)。
可用于一般的条件表达式中

for(…;…;…)

for((i=0;i<10;i++));do
     echo $i
done

condition?a:b

: $((b=a<10?1:0))

注意, :在这里只是为了避免赋值的副作用

含有\x00的字符串

将含有\x00的字符串赋值给变量时,会产生截断:

a=$'aa\x00adf'
"$a"
--> bash aa: command not found
echo "${#a}"
-->2

如上所见,字符串只被识别到2个字符。

如果需要传递这样的字符串,可以通过管道进行操作。

echo -ne 'ab\x00c'|wc -c
--> 4

字符串

参数替换(Parameter Substitution)

变量形式变量未声明变量已声明,值为null变量不为null说明
${a-default}default$a$a使用默认值:变量只需声明即可避免使用默认值
${a:-default}defaultdefault$a使用默认值:变量需声明且值非空才能避免使用默认值
${a=default}a=defaulta不变a不变设置默认值:变量只需声明即可避免使用默认值
${a:=default}a=defaulta=defaulta不变设置默认值:变量需声明,且值非空才能避免设置默认值
${a+alt_value}nullalt_valuealt_value使用可选值:变量需声明
${a:+alt_value}nullnullalt_value使用可选值:变量需声明,且值非空
${a?err}错误正确正确变量断言:变量需声明
${a:?err}错误错误正确变量断言:变量需声明,且值非空

注:上面的每种形式都有一个:的版本,:的目的是区分变量为null的两种情况中,未声明和已声明但是为null的情况。 基本上,不带:的版本,只要已声明,无论值是null或非null,结果都是一致的。而带:的版本,未声明和已声明但值null是一致的。
总的来说, 不带:的版本,只关心是否声明;而带:的版本,只关心值是否为null.

字符串

LDP-manipulate strings
${a:from:len} 子串,也可用于数组

${}替换:规则: #表示开头,%表示结尾, /表示替换
/# 表示匹配开头并替换, /%表示匹配末尾并替换
/表示替换所有

expr

Here document

基本形式,进行变量替换

user:dar export NAME=test
user:dar cat << EOF
> name=$NAME
> EOF
name=test

带引号(单引号和双引号一样),不进行变量替换

user:dar export NAME=test
user:dar cat << "EOF"
> name=$NAME
> EOF
name=$NAME

无需对变量进行双引号引用(不会进行expand)

字符串作为输入

当使用变量作为内联字符串输入时,无需对变量进行双引号引用

user:dar a="a b c
> d e
> "
user:dar cat -e <<< $a
a b c$
d e$
$

词法环境

词法环境,也就是lex environment, 是指在

${s/A/B}, 其中A和B都是词法环境,A和B会作为一个单独的词法环境进行解析,而不受外围的语法影响。
此外,$(S)中S也创造了一个词法环境

在=~ 的右侧,也创建了一个词法环境,但是注意,在pattern中可以使用 $’\n’来表示特殊字符

$ [[ ab$'\n'c =~ [$'\n'] ]];echo $? 
0

=~

关于Pattern,参考Regular Expression Posix Extended

[[ ]]

[[]]无需对变量进行双引号引用,它们不会像 [ 那样([实际上是命令,而 [[ 是bash内建的操作符)

user:dar a="a b c"
user:dar [[ -n $a ]];echo $?
0
user:dar [ -n $a ]];echo $?
-bash: [: missing `]'
2

变量赋值(包括拼接,不会进行expand)

$ a="ab cd"
$ b="  ef  z"
$ c=$a$b
$ echo "$c"
ab cd  ef  z

字符串替换

只有${…/…/…}在语法层面上符合 / … /的格式即可,即使变量中含有/分隔符,也不会进行expand

a=' b c/ d '
b=' x f/ c '
c=X.$a$b.X
echo "$c"  # X. b c/ d  x f/ c .X
echo ${c/$a$b/Z}  # X.Z.X

需要对字符串变量进行双引号引用

文件重定向

当包含文件名的变量含有分隔符号(空白符,换行等)时,在重定向中必须使用双引号引用

user:dar echo yes > "a b.txt"
user:dar f="a b.txt"
user:dar cat < "$f"
yes
user:dar cat < $f
-bash: $f: ambiguous redirect

Code Snippets

for each line

使用read读取文件的每一行,使用’'进行分割

IFS=$'\n'
read -d '' -a arr <<< $data

解释:
IFS=$’\n’用于设置域分割符合是换行符,这样read数组的时候就是会使用换行符作为分隔
-d ‘’ 用于设置delimeter,用于指示read读取所有的输入
-a arr 赋值到数组中
<<< $data 字符串输入重定向

Example Usage

lsof查找

ps显示进程

gitm.sh显示当前git仓库消息

#!/usr/bin/env bash
git log --format=%s -n1 "$@"

kill-port.sh杀掉端口对应的进程

#!/usr/bin/env bash
if [[ -z $1 ]];then
    echo "requires port" >&2
    exit 1
fi
pid=$(lsof -i ":$1"|tail -n1|awk '{print $2}')
if [[ -z $pid ]];then
    echo "port not open:$1" >&2
    exit 1
fi
echo "killing pid=$pid for port=$1" >&2
kill -9 "$pid"

显示git分支

# Git branch in prompt.

parse_git_branch() {
  git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}

PS1="\u@\h \W\[\033[32m\]\$(parse_git_branch)\[\033[00m\] $ "

PS1定制

参考:http://ezprompt.net/

\A = 11:38
\u = 用户名
\h = hostname
PS1="\u@\h \w\[\033[32m\]\$(parse_git_branch)\[\033[00m\] \A  "

效果:

nick@Desktop ~/project (develop) 11:41 

如果目录显示过于长,可以将换行

PS1="\u@\h \w\[\033[32m\]\$(parse_git_branch)\[\033[00m\] \A \n "

特殊图标:
unicode查询
苹果:echo -ne '\xef\xa3\xbf'
Linux企鹅:🐧 echo -ne '\xf0\x9f\x90\xa7'

目录缩写

shortpath(){
echo ${PWD/#'/home/dar/wpath'/'$W'};
}
PS1='\u@\h \e[33m$(shortpath)\[\033[32m\]$(parse_git_branch)\[\033[00m\] \A '

IP

用于区分

ipaddr(){
	addr=$(ip addr show eth0 |awk '/inet /{print $2}'|cut -d '/' -f1)
	echo -n "$addr"
}
# use PS1='\u@$(ipaddr)'

忽略不可打印的字符

在定制PS1时,应当确保指示bash忽略带颜色的字符,不然就可能导致输入提前换行。
http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/nonprintingchars.html

汇总

Linux PS1配置:

parse_git_branch() {
    git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
  }
shortpath(){
	p=${PWD/#'/Users/bytedance'/'~'}
	p=${p/#'/home/xiaohuadong'/'~'}
	p=${p/#'~/Projects/webcast'/'$GOPATH'}
	p=${p/#'$GOPATH/src/code.byted.org/webcast'/'$W'}
	 echo -n "$p"
}
ipaddr(){
	addr=$(ip addr show eth0 |awk '/inet /{print $2}'|cut -d '/' -f1)
	echo -n "$addr"
}

# the linux penguin icon
PENGUIN=$(echo -ne '\xf0\x9f\x90\xa7')
# 
APPLE=$(echo -ne '\xef\xa3\xbf')

# PS1='\u@\h:\w \A\n\$ '
PS1='\u@$(ipaddr) \e[33m$(shortpath)\[\033[32m\]$(parse_git_branch)\[\033[00m\] \A \n$PENGUIN '

Shell编写指南

几大原则

关于 /bin, /sbin, /usr/bin, /usr/local/bin, 你需要了解 /usr/bin是系统层级的, /usr/local/bin是organization层级的,因此,对于env这样的工具,应当指定 /usr/bin/env

此外,对于要不要将子工具暴露到路径中,这是一个关于如何组织代码的问题,你当然需要避免将子工具暴露到路径中。这样一来,你就只能依赖某些特殊的项目

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值