bash及zsh语法
执行命令或文件
以下对zsh与bash同时成立
下述文件
为shell语言的脚本,常见为.sh
文件或.bash_xxxx
类文件
在当前shell执行
- 使用当前shell的变量/函数/当前路径, , 不论是否export
- 创、变、删的 变量/函数/当前路, 不论是否export, 都会保存在当前shell里面
source 文件
(等价于 . 文件
)
- 文件不必有可执行权限
命令
, { 命令块;}
, 循环体/条件体
{
后要有空格,}
前要有;
(但无需空格), 才能在bash运行, 不然只能zsh下运行{ 命令块;}
, 循环体/条件体, 即相当于运行了匿名函数{ 命令块;}
与命令块
产生相同的标准输出和标准错误
开新shell执行
./文件
或 <某种SHELL> 文件
-
开新shell运行, 会加载执行的shell 的 profile 文件 (例如
.profile
,.zshenv
) -
继承全局变量(即export了的变量):
- 新shell继承 (即copy) 当前shell中 export 了的变量, 不继承当前shell中未export的变量
- 新shell新建/改变 的 变量/函数/当前路径, 不论新shell是否export它们, 在新shell结束后注销, 不会被带回当前shell
- 新shell结束后, 当前shell的变量均无变化
-
执行的shell:
<某种SHELL> 文件
, 包括sh 文件
, 文件不必有可执行权限./文件
等价于sh 文件
,但要求文件有可执行(x
)权限,授权方式chmod +x 文件
)
-
shell的区别
- sh: 全称 POSIX shell,
./文件
所使用的shell, 所有 unix 系统共有- POSIX: 可移植操作系统接口Portable Operating System Interface of UNIX
- bash: 全称 Bourne Shell, 增强版的sh, 更常用, 大多数Linux发行版默认的交互shell
- zsh: 全称 z shell, 类似bash的shell, 改善交互体验
- sh: 全称 POSIX shell,
-
$SHELL 文件
:用当前默认的交互shell(可能是zsh或bash等)执行,文件不必有可执行权限$SHELL
是当前默认的交互shell$SHELL -c '命令'
:用当当前默认的交互shell执行chsh -s $(which <shell名>)
: 修改默认的交互shell
开子shell执行
- 不是另启一个新shell,故不会加载执行的shell 的 profile 文件 (例如
.profile
,.zshenv
) - 开子shell, 与当前shell是同一个shell类型 (即, 是sh,bash, 还是zsh)
- 继承父shell的所有变量, 不论是否export
- 子shell新建的、改变的 变量/函数/当前路径 不会被带回父shell,不论是否使用export, 在子shell结束后注销, 子shell结束后父shell的变量均无变化
value=`命令`
,等价于 value=$(命令)
- 输出返回到value,value为一个字符串,可以有换行符;value不是数组
(命令块)
- 与
命令块
产生相同的标准输出和标准错误 (
后与)
前无需空格, 有也能运行;命令块
后无需;
, 有也能运行.
例子
pwd
(cd ..; pwd)
pwd
返回
/home/you/ENV/CONF /home/you/ENV /home/you/ENV/CONF
然而
pwd
cd ..; pwd
pwd
返回
/home/haoyu/ENV/CONF /home/haoyu/ENV /home/haoyu/ENV
管道 命令1 | 命令2
命令1
(非管道结尾的命令), 均是在当前shell分别开子shell,命令1
的修改/创建的变量(不论是否export), 不会留给命令2
与 后续的父shell
a=1; a=2 | echo $a ; echo $a
a=1; export a=2 | echo $a ; echo $a
# bash 和 zsh 下均输出
# 1
# 1
命令2
(管道结尾的命令) 在zsh下是 在当前shell执行, 改/创建的变量(不论是否export), 会留给后续的父shell命令2
(管道结尾的命令) 在bash下是 开子shell执行, 改/创建的变量(不论是否export), 不会留给后续的父shell
a=1 b=1 c=1; a=2 | b=2 | c=2; echo $a $b $c
# zsh下输出 1 1 2
# bash下输出 1 1 1
管道与循环体/管道组合
-
命令|循环体
, 在bash中循环体是子shell执行; 在zsh中循环体是当前shell执行 -
循环体与重定向 (
<<<
<<
<
< <(...)
)组合起来, 则循环体在bash和shell中均是用当前shell执行 -
故而, 如需修改此代码块外的变量 (例如给一个array加成员, 给计数器加值), 建议能用重定向, 就用重定向写, 以便zsh和bash兼容.
即, 要写成
循环或条件体 <<< "${string}" 循环或条件体 < <( 命令块 )
而不要
echo -E "${string}" | 循环或条件体 ( 命令块 ) | 循环或条件体 { 命令块; } | 循环或条件
例如
array=(); while IFS= read -r line; do array+=("${line}"); done <<< \ 'asda'$'\n''adasadasd'$'\n''asdasdasdassd' declare -p array # bash 输出 # declare -a array='([0]="asda" [1]="adasadasd" [2]="asdasdasdassd")' # zsh 输出 # typeset -a array=( asda adasadasd asdasdasdassd )
array=(); echo -E 'asda'$'\n''adasadasd'$'\n''asdasdasdassd' | \ while IFS= read -r line; do array+=("${line}"); done declare -p array # bash 输出 # declare -a array='()' # zsh 输出 # typeset -a array=( asda adasadasd asdasdasdassd )
代码块的性质
代码块的组合命令
不论哪种代码, 均有以下性质
帽 代码块 靴 | 命令
: 表示对整个代码块的输出 经过管道 送给命令
帽 代码块 靴 >文件
: 表示对整个代码块的输出 写入到文件
.>>
同理
循环体
for/while xxx do; 代码块; done 管道或写入文件
是对所有轮的输出 整体执行一次.for/while xxx do; { 代码块; } 管道或写入文件; done
是每轮循环输出 执行一次管道/文件写入
例如
for i in {1..3}; do
echo $i
done | sort -r # 降序
# 结果为
# 3
# 2
# 1
条件体
if ... if 管道或写入文件
不论对于哪个条件分支, 或不满足任何分支条件的情况, 均会执行
例如
rm result
if [ 1 -ne 1 ]; then
echo '1 -ne 1'
echo '1 != 1'
fi > result
# 结果为 创建了一个空的 result 文件
rm result
if [ 1 -ne 1 ]; then
{
echo '1 -ne 1'
echo '1 != 1'
} > result
fi
# 结果为 没有创建result文件
获得命令输出
$(...)
`....`
输出返回为匿名字符串
以下对zsh与bash同时成立
子shell执行
`xx`
完全等价于$(xx)
:用当前shell开子shell执行xx
命令,将其返回为无名变量。例如
echo `ls`
echo $(ls)
result=`ls`
result=$(ls)
`bash -c "xx"`
完全等价于$(bash -c "xx")
:用bash执行xx
命令,将其返回为无名变量
输出
`xx`
完全等价于 $(xx)
只获得xx
命令的正常输出(>&1
),不获得
xx
的报错报警输出(>&2
)仍然打印在屏幕- 若想
xx
重定向到文件的输出, 则写>文件
例如:
f()
{
echo 'file' > /dev/null # 报错报警
echo 'warring' >&2 # 报错报警
echo 'normal' >&1 # 正常输报警
echo 'normal' # 默认是正常输报警
}
return_string=$(f)
# 输出如下
warring
echo $return_string
# 输出如下
normal
normal
<(...)
输出返回为匿名文件
以下对zsh与bash同时成立
<(命令)
, 将命令的输出返回为匿名文件.
注意:
- 用当前shell开子shell执行
xx
命令 - 此文件只获得
xx
命令的正常输出(>&1
), 报错报警输出(>&2
)仍然打印在屏幕
辨析:
命令 < <(echo -E "${string}”)
等价于命令 <<< "${string}"
例如:
-
比较两个目录的差异
diff <(ls dirA) <(ls dirB)
-
逐行读取多行字符串
while IFS= read -r line; do echo "$line" done < <(echo -E "${string}")
等价于
while IFS= read -r line; do echo "$line" done <<< "${string}"
各种引号
以下对zsh和bash皆成立
转义
创建字符串, 各种引号
赋值给字符串时:
-
单引号:所有都不转义
-
$'xxx'
: 仅\某
转义 -
双引号,
<<EOF
,<<-EOF
:${xxx}
(变量)、`xx`
$()
(命令返回) 转义, 用\$
\`xxx\`
取消转义;\某
不转义
输出
从被输出的字符串, 到输出的字符串, 这个过程中:
-
echo -E 字符串
: 输出\某
不转义 -
echo [-e] 字符串
: 输出\某
转义,-e
即默认选项 可省去
双引号的优先级
-
”$(..."...${variable}..."....)”
及”`..."...${variable}..."....`”
均表示: 含有将variable变量的字符串"...${variable}…”
, 用于某个子shell命令..."...${variable}..."....
, 其结果返回为一个字符串. 最外围的双引号不会跨过$(…)
或`…`
的边界与其内部的双引号成对解析. 例如 -
echo "$([ "${USER}" = "${USER}" ] && echo yes)" # 返回 yes
* `”$((..."...${variable}..."....))”` 会被解析成 `”$((...”` 字符串, `....` , `${variable}` 变量, `...`, ` "....))”` 字符串的concat. 例如 ```bash a=1; b=2 echo "$(("$a" + "$b"))" # 返回 # zsh: bad math expression: illegal character: "
局部变量
- 仅函数内可见的变量,只需首次用时写
local
,之后不用再写了。但不可用于非函数内 (如脚本中 不在函数里之处), 例如
f() {
local variable=xxx
variable=yyy # 再次使用,无需声明local
local i=
# 不可写作 local i; 不然若之前已经创建了local i, 则此处会输出 "i=xxxx"
for i in "$@"; do
# 不可写作 for local i in "$@"; do
# xxxx
done
}
f
echo ${varible} # 返回为空,因为在全局变量中,没有名为variable的变量
local variable=
: local variable赋值为’’
, 用于local variable
: 若无local variable, 则创建此variable; 若有local variable, 则显示"vairable=xxxx".bash/zsh/sh 文件.sh
及其./文件.sh
运行脚本,脚本中所创、改、删的变量/函数不会带到运行它环境中(除非export
),无需手动释放创建的变量/函数source 文件.sh
(即. 文件.sh
),脚本中所创、改、删的变量/函数会带到运行它环境中(不论是否export
),故需手动释放创建的变量/函数
unset -f 函数名
unset -v 变量名 # -v 可缺省
判断
以下适用于bash
和zsh
概述
if ( 判断语句 ); then
do_something
fi
if [ 判断语句 ]; then
do_something
fi
if [[ 判断语句 ]]; then
do_something
fi
或
# 否定条件则写作:
( 判断语句 ) && do_something # ! ( 判断语句 ) && do_something 也即 ( ! 判断语句 ) && do_something
也即 判断语句 && do_something # 也即 ! 判断语句 && do_something
[ 判断语句 ] && do_something # ! [ 判断语句 ] && do_something
[[ 判断语句] ] && do_something # ! [[ 判断语句 ]] && do_something
判断语句
( 判断语句 ) 也即 判断语句 |
含义 | 示例 |
---|---|---|
command -v 命令 > /dev/null |
判断命令知否存在(命令可以使alias以及自定义的shell函数) | if command -v pwd > /dev/null; then 也即 if ( command -v pwd > /dev/null ); then |
注:[ command 命令 2>&1 | grep 'command not found'] |
辨析:判断命令知否存在(命令为系统原生命令,不包含可以使alias以及自定义的shell函数) | [ command ls 2>&1 | grep 'command not found'] |
[ $? -eq 0 ] |
$? 表示前面所有代码里是报错的编码是啥, 为0<=>无错误 |
[ $? -ne 0 ] && echo Error |
[ 判断语句 ] (也可用[[ ]] ) |
含义 | 示例 |
文件比较 | ||
-e file | 存在文件或文件夹,或存在一个有效(最终(可以符号逻辑递归)指到文件或文件夹)的符号链接 | [ -e /var/log/syslog ] |
-d file | 存在且为目录 | [ -d /tmp/mydir ] |
-f file | 存在且为常规文件 | [ -f /usr/bin/grep ] |
-L file | 为符号链接,不论是否有效 | [ -L /usr/bin/grep ] |
-r file | 存在且当前用户可读 | [ -r /var/log/syslog ] |
-w file | 存在且当前用户可写 | [ -w /var/mytmp.txt ] |
-x file | 存在且当前用户可执行 | [ -L /usr/bin/grep ] |
-c file | 存在且为字符特殊文件 | |
-b file | 存在且为块特殊文件 | |
-s file | 存在且文件大小非0,即文件存在且非空 | |
-t file | 文件描述符(默认为1)指定的设备为终端 | [ -t 1 ] 表示当前命令会显示在屏幕,即当前处在交互终端下 |
file1 -nt file2 | file1 比 file2 新 | [ /tmp/install/etc/services -nt /etc/services ] |
file1 -ot file2 | file1 比 file2 旧 | [ /boot/bzImage -ot arch/i386/boot/bzImage ] |
字符串比较 | 要用引号 | |
-z string | 长度为零,即串空 | [ -z “$myvar” ] |
-n string | 长度非零,即串非空 | [ -n “$myvar” ] |
-z ${var+x} |
变量var是否有定义($var='' 即var无定义) |
[ -z “${myvar+x}" ] |
string | 与上等价 | [ “$myvar” ] |
string1 = string2 | string1 与 string2 相同 | [ “$myvar” = “one two three” ] |
string1 != string2 | string1 与 string2 不同 | [ “$myvar” != “one two three” ] |
布尔值 | ||
"$mybool" = true |
是否真 | [ "$mybool" = true ] |
"$mybool" = false |
是否假 | [ "$mybool" = false ] |
"$myboo1l" = "$mybool2" |
是否同 | [ "$myboo1l" = "$mybool2" ] |
"$myboo1l" != "$mybool2" |
是否异 | "$myboo1l" != "$mybool2" |
[[ 判断语句 ]] |
含义 | 示例 |
匹配 | ||
"$string" =~ "$substring” 及 "$string" =~ 'xxxx' |
bash中:string是否包含substring (普通字符串); zsh中是:string是否匹配正则表达式,例如 | [[ "$string" =~ "$substring" ]] 及 [[ "$string" =~ "balabala" ]] |
"$string" =~ $正则表达式 及 "$string" =~ xxxx |
在zsh和bash中:是否string包含**正则表达式 **,例如 |
[[ "$string" =~ ^[0-9]+$ ]] |
"$string" = $通配符 及 "$string" = xxxx |
在zsh和bash中:string是否能匹配**通配符 **,例如 |
[[ "$path" = */images/* ]] |
算术比较 | ||
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 | >= | [[ 3 -ge $mynum ]] |
注意
- 运算符前后要有空格至少一个
[、[[
后]、]]
前要有空格至少一个
其他特殊判断
式子 | 含义 | 说明 |
---|---|---|
[ "${variable-no}" != no ] |
变量variable 有定义 |
若变量 variable 无定义, 则${variable-xxxx} 返回“xxxx”variable 有定义, 则返回${variable} |
variable= : 等价于 variable=‘’ , 故有定义unset variable : 注销variable, 故无定义 |
逻辑运算
以下以[ 判断语句 ]
为例,[[ 判断语句 ]]
同理
逻辑运算 | 含义 | 例 |
---|---|---|
[ 判断语句 ] && [ 判断语句 ] |
与,次次优先 | |
[ 判断语句 ] || [ 判断语句 ] |
或,次次优先 | |
! [ 判断语句 ] 或 [ ! 判断语句 ] |
非,次优先 | ! 和 [ 中间必需有至少一个空格 |
( 上述三种运算 ) |
结合,最优先 | if ! ( [ 判断1 ] || [ 判断2 ] ) && [ 判断3 ]; then dosomething; fi |
[[ ... ]]
与 [ ... ]
辨析
- linux的sh不支持
[[...]]
- 在zsh,bash,mac的sh,
linux的sh中:- 凡是能用
[...]
的判断语句, 都能用[[...]]
[[...]]
可以把&&
,||
,()
,!
写在里面: 例如[[ 1 = 1 && 2 =2 ]]
,[[ ( ! 判断1 || 判断2 ) && ( 判断3 )]]
- 凡是能用
- 在linux与mac的sh/bash/zsh中,
[…]
,[[...]]
. 它们都可以把,&&
,||
,()
!
写在里面: 例如[ ! 1 = 2 ]
[[ ! 1 = 2 ]]
输入
get one char
answer=$(bash -c "read -n 1 -p '<要显示的提示句> ? [Y|N]' c; echo \$c"); echo
# 调用bash执行read -n 1 -p '<要显示的提示句> c', 即将提示句显示,等待输入字符,抓取即赋值给c
# 将c返回到answer
# echo:换行
- 适用于:zsh、bash、sh、
./文件
运行可执行文件、任何当前终端中 - 功能:等待输入,一旦输入一个字符(回车也算一个字符),立即结束等待,将这个字符返回给
answer
变量
注:
-
请不要使用
echo -n '<要显示的提示句> ? [Y|N]' answer=$(bash -c "read -n 1 c; echo \$c"); echo
因为使用sh、
./文件
运行可执行文件 的方法运行上述命令,echo不支持-n
参数,会输出"-n"
询问Y/N
while true; do
# echo - '提示?[Y/n]'
answer=$(bash -c "read -n 1 -p '<要显示的提示句>? [Y/N] ' c; echo \$c"); echo
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
# 回答是则如何操作
break
elif [ "$answer" = "n" ] || [ "$answer" = "N" ]; then
# 回答否则如何操作
break
else
echo 'input Y/y or N/n please!'
fi
done
例如
<提示句>? [Y|N]n
# 回答否的操作
重定向
输入重定向
<
与 <<<
命令 < 文件
等价于: 执行命令
然后在 stdin 输入 {cat 文件
之所得}命令 <<< 字符串
等价于: 执行命令
然后在 stdin 输入字符串
例如:
cat <<< "${HOME}"
/home/haoyu/ENV/CONF
cat <<< '${HOME}'
$HOEM
sed 's/a/b/g' <<< "aaa"
bbb
cat < 文件
等价于 cat 文件
echo 'aaa' > file
sed 's/a/b/g' <<< file
bbb
读取文件到变量
-
不保留结尾空白字符和开头空白字符
read -r -d '' 变量 < 文件
-r
: raw string, 即原始的字符串-d 分隔字符
: 以分隔符将分割得到的字符串数组给变量. 当其为''
即不要分割.
-
保留开头空白字符, 但不保结尾空白字符
变量=$(<文件)
# 等价于
变量=$(cat 文件)
-
保留开头/结尾空白字符
file_to_var() { local file_name="$2" local var_name="$1" local file_to_var_var='' while IFS= read -r line ; do file_to_var_var+="$line"$'\n'; done < $file_name file_to_var_var="${file_to_var_var:0:-1}" eval "$var_name"'="${file_to_var_var}"' } file_to_var 文件 变量
<<
多行字符串
-
总述
命令 <<[-]EOF 多行内容1 多行内容2 EOF
- 以上等价于: 执行
命令
然后将<<[-]EOF
与EOF
之间的字符串 输入到 stdin
- 以上等价于: 执行
-
<<
/<<-
与EOF
间可以有空格EOF
可以改成任意[a-zA-Z_]+
的字符串
-
<<-
表示去掉多行内容再每行开头的所有 tab,<<
则不去掉- 结尾的
EOF
必需在行首, 无缩进 - 不转义
\x
, 仅转义${xxx}
$xxx
$(xxx)
`xxx`
, 取消转义需$
写做\$
,`
写做\`
.
- 结尾的
-
输出多行字符串:
cat <<EOF 多行内容1 多行内容2 EOF
输出
多行内容1 多行内容2
-
取消tab缩进, 但空格缩进不会取消:
cat <<-EOF
多行内容1 # 开头是 tab, 不是空格
多行内容2 # 开头是 tab, 不是空格
EOF
输出
多行内容1
多行内容2
-
多行字符串写到文件
-
法一:
>
无法以 sudo 形式写入到文件
cat > 文件 << EOF
多行内容1
多行内容2
EOF
- 法二 [更灵活, 推荐之] : 允许以 sudo 形式写入到文件
cat << EOF | [ sudo ] tee [-a] 文件 [ >/dev/null ]
多行内容1
多行内容2
EOF
-
多行字符串赋值给变量
-
不保留结尾空白字符和开头空白字符
read -r -d '' var << EOF
多行内容1
多行内容2
EOF
# 或将命令块的返回作为多行字符串返回给var
read -r -d '' var < <(命令块)
- 保留开头/结尾空白字符
var=''; while IFS= read -r line ; do var+="$line"$'\n'; done << EOF && var="${var:0:-1}"
多行内容1
多行内容2
EOF
输出重定向
>
与 >>
写文件的模式
>
: write模式:即若有文件,则重写文件;若无文件,则新创而写之>>
: append模式:即若有文件,则追加到文件尾;若无文件,则新创而写之
标准输出、标准错误重定向到文件
命令 >文件名
=命令 1>文件名
:重定向标准输出到文件,write模式命令 2>文件名
:重定向标准错误到文件,write模式- 上述
>
改为>>
,即变成append模式 - (
>
or>>
) 与文件名
之间可以有空格, 但与&1
&2
之间不能有空格
常用文件名
/dev/null
:可无限写入、但不保存内容的“黑洞“文件&1
:标准输出,会显示在显示屏&2
:标准错误,也会显示在显示屏
标准输出、标准错误相互重定向
2>&1
:将标准错误重定向到标准输出——标准错误不再有原标准错误的内容,而标准输出同时有远标准错误、标准输出的内容。1&>2
:将标准输出重定向到标准错误,效果同上理
重定向的组合命令:重定向从后往前执行,如 命令 >文件名 2&>1
先执行 2&>1
后执行 >文件名
命令 &>文件名
=命令 >文件名 2&>1
(≠命令 2&>1 >文件名
):标准输入和输出都重定向到文件,write模式命令 &>>文件名
=命令 >>文件名 2&>1
(≠命令 2&>1 >>文件名
):标准输入和输出都重定向到文件,append模式- 上述不等的原因是,重定向从后往前执行
缺点:
>
和>>
不支持 sudo 重定向: 例如当执行sudo 命令 >文件
, 仅命令
是用 sudo 执行的, 重定向写文件用的是普通用户, 故若此文件仅 root 可写, 则会写入向失败. 可以使用tee
来解决这个需求
tee [推荐]
基础用法
命令 | tee 文件
: write 模式 将命令的标准输出写在文件, 同时输出在终端命令 | tee -a 文件
: append 模式 将命令的标准输出写在文件, 同时输出在终端
变体用法
命令 | tee [-a] 文件 >/dev/null
: 仅将命令的标准输出写在文件, 标准输出不输出在终端, 标准错误才输出在终端命令 | tee [-a] 文件 >/dev/null 2>&1
: 仅将命令的标准输出写在文件, 标准输出/标准错误均不输出在终端命令 2>&1 | tee [-a] 文件
: 将命令的标准错误与标准输出写到文件, 同时输出在终端命令 | sudo tee [-a] 文件
: 普通用户执行命令, sudo 模式重定向其标准输出到文件sudo 命令 | sudo tee [-a] 文件
: sudo 用户执行命令, sudo 模式重定向其标准输出到文件
以上变体均可组合, 如:
sudo 命令 | sudo tee [-a] 文件 >/dev/null
: sudo 用户执行命令, sudo 模式重定向其标准输出到文件. 仅将命令的标准输出写在文件, 标准输出不输出在终端, 标准错误才输出在终端
优点:
- 支持写到文件, 同时显示或不显示在终端
- 支持 sudo 写入到文件
dd
基础用法
命令 | dd of=文件
: write模式, 将命令的标准输出写在文件, 其标准输出不输出在终端, 标准错误才输出在终端命令 | dd of=文件 oflag=append conv=notrunc
: append模式, 将命令的标准输出写在文件, 标准输出不输出在终端
变体用法
命令 2>&1 | dd of=文件
: write模式, 将命令的标准输出/标准错误写在文件, 均不输出在终端命令 | sudo dd of=文件
: 用普通账号运行命令; write模式, 用 sudo 模式将命令的标准输出写在文件, 标准错误才输出在终端命令 | sudo dd of=文件
: 用 sudo 运行命令; write模式, 用 sudo 模式将命令的标准输出写在文件, 标准错误才输出在终端
缺点:
- 写到文件的同时无法显示在终端
参数传输
xargs命令
前置命令 | xargs -0 后续命令 -选项 参数 -选项 参数 # 传到最后一个参数后面做参数
前置命令 | xargs -0 -I{} 后续命令 -选项 {} -选项 参数 # 传给{}所在位置的参数
-0
参数表示用null
当作分隔符。
后台运行
前台运行+重定向输出
前台运行,在这个窗口输新命令无法立即执行
但又看不见输出的结果,输出都写入了文件中
# 输出写到文件,文件原来的内容全删
{
命令1
命令2
...
} >& 文件名 # 等价于 > 文件名 2>&1
# 输出追加写到文件名的屁股后面
{
命令1
命令2
...
} >>& 文件名 # 等价于 > 文件名 2>&1
> 文件名
表示将1
(正常输出)写到文件名
,文件原来的内容全删>> 文件名
表示将1
(正常输出)追加写到文件名
的屁股后面2>&1
表示将2
(错误、警告)都用1
(正常输出)输出- 若
文件名
=/dev/null
,表示不显示到命令行,也不写到任何真实文件。/dev/null
是一个可无限写入、但不保存内容的“黑洞“文件
后台运行结束插播短信
{
命令1
命令2
...
} &
后台执行命令1,2…
- 开始启动时显示
[1] 120892
用户名@服务器名 (在此可以干别的事情)
- 完成时显示
用户名@服务器名 (插播短信"完成进程")
[1] + 120892 done { 命令1; 命令2; ... }
用户名@服务器名
后台运行结束不插播短信+输出重定向
( {
命令1
命令2
...
} & ) >& <log_file>
&
:后台执行命令1,2…
>&
: 并将错误、警告、正常输出都写到 <log_file>
- 程序开始和结束都不会有显示,要看输出需开`文件名
后台运行结束插播短信+输出重定向
( {
命令1
命令2
...
} ) >& <log_file> &
后台执行命令1,2…
- 开始启动时显示
[1] 120892
用户名@服务器名 (在此可以干别的事情)
- 完成时显示
用户名@服务器名 (插播"完成进程")
[1] + 120892 done { 命令1; 命令2; ... }
用户名@服务器名
- 其余错误、警告、正常输出都写到 <log_file>
- 若 <log_file> =
/dev/null
,表示不显示到命令行,也不写到任何真实文件。/dev/null 是一个可无限写入、但不保存内容的“黑洞“文件。
多进程后台运行+运行完集中输出
rand="$RANDOM"
log_file="/tmp/parallels$rand"
(
for 范围; do
{
命令
} >> 2>&1 $log_file &
# &: 多进程后台运行
# >> $log_file # stdout 存入$log_fil
# >> 2>&1 $log_file # stdout, warning, error 存入$log_fil
done
wait
) >& /dev/null # stdout, warning, error, 包含 后台运行开始和结束的短信
cat $log_file # 集中输出运行结果
rm $log_file
数学运算
$(( 算式 ))
等价于$(expr 算式)
等价于 `xpr 算式`
算式中运算符与数值或变量之间必需有空格, 例如
a=1
echo $((1 + 2)); echo `expr 1 + 2`; echo $(expr 1 + 2); expr 1 + 2
echo $(($a + 2)); echo `expr $a + 2`; echo $(expr $a + 2); expr $a + 2
# 输出皆 3
字符串
以下的 string
可以取做 1
2
…, 表示是函数或脚本的第1,2,…个参数
如 ${1:4}
表示第函数的1个参数的左起第4个字符(0开始计数)及往后的子串
字符串长度
字符串长度 (含颜色控制符)
zsh bash通用
${#string}
字符串长度 (去除颜色控制符)
zsh bash通用, 可以通过如下方法, 去除红色的控制符
string=$(echo 'abc 123abc123' | grep --color=always 123)
length_string=${#string}
# 等价于
string_without_red="$(cat -v <<< "${string}" | sed -E "s/(\^\[\[01;31m\^\[\[K|\^\[\[m\^\[\[K)//g")"
length_string_without_red=${#string_without_red}
echo "$string"
echo "$length_string"
echo "$string_without_red"
echo "$length_string_without_red"
输出
abc 123abc123
47
abc 123abc123
14
字符串取子串
{string:始:长} (从0开始)
bash/zsh 支持, {string:始>=0:止<0}
仅zsh支持
bash或zsh支持 | zsh支持,bash不支持 | bash或zsh里这样写得不到想要的 | 结果(用python表示) |
---|---|---|---|
{string:始:长} |
{string:始:止} |
||
始>=0 左起 始<=-1 右起 长>=0 |
始>=0 左起 始<=-1 右起 止<=-1 右起 |
||
若始=-m<=-1 且 长>=0 | 若始=-m<=-1 且 止=-n<0 | ||
${string:始} ${string:始:止} |
string |
||
${string:${ #string}-m:长} |
string[-m,len(string)-m+长] |
||
${string:${ #string}-m:-n} |
string[-m,-n] |
||
止=倒数第 n 个 (n>0) | **若始>=0 且 止=-n<0 ** | ||
${string:始:-n = ${string:始:{ #string}-n-始} |
${string:始:-n} |
string[始:-n] |
|
若始>=0 且 长>=0 | |||
${string:始} |
${string:始:} zsh无法解析会报错,bash返回空字符串 |
string[始:] |
|
${string:始:长} |
string[始:始+长] 即string[始:min(始+长,len(string)+1] |
||
${string:始:1} |
${string:始} 结果是 string[始:] |
string[始] |
|
${string:始:0} |
'' |
说明:
${string:x:y}
:x
y
可以直接写四则运算,如${stinrg:0*1+1:${
#string}/2+1}
$string[始,止] (从1开始)
仅 zsh 支持
本写法zsh支持,bash不支持。始,止皆从1开始,可以是负数。
-
取一个字符
$string[n]
取值:$n \in (-\infty,+\infty)$
结果:
if n<0: n=max(1, n+len(string)) if n in range(1, len(string)+1): return string[n-1] # 取出的是第n个字符 else return '' # 取出的是''
-
取一段字符
$string[m,n]
取值:$m,n \in (-\infty,+\infty)$
结果:
if m<0: m=max(1, m+len(string)) if n<0: n=max(1, n+len(string)) if m<=n: return string[m-1,n] # 取出的是第[m,n] 区间的字符 else: return '' # 取出的是''
遍历字符串
以下适用于zsh和bash
for ((i=0; i<${#string}; i++)); do
echo "${string:$i:1}"
done
字符串拼接
以下适用于zsh和bash
string="${sting1}${string2}"
string+="${sting2}"
string+='xxxxxx'
字符串转数组
string="1:2:3::4 :5"
zsh专用
array=("${(@s/【分割字符串】/)string}")
array=("${(@s/:/)string}")
array=("${(@s/\n/)string}")
bash专用
-
使用空白字符(正则表达式为
\s+
, 即连续尽可能多(>=1个)的tab/换行/空格等)分割:array=(${string})
或
read -a array <<< $string
bash与zsh通用
-
使用任何单个字符分割
注意, 连续两个【 分割字符】, 分割开会得到其中间的一个 “” 字符串, 这与bash专用的分割策略不同
hadopt=false [ -n "$ZSH_VERSION" ] && [ "$(setopt | grep shwordsplit)" != '' ] && \ hadopt=true && setopt sh_word_split # 若为 zsh则开sh_word_split选项 OLD_IFS="$IFS" ; IFS="【 分割字符】" # " # 必需是单个字符,但可以是汉字 # 如果是转义字符需加 $'\某',如换行,需要写成 $'\n',必需是单引号 array=($(echo -E "$string")) IFS="$OLD_IFS" [ -n "$ZSH_VERSION" ] && [ "$hadopt" = false ] && unsetopt sh_word_split # 若原先没开此选项则关之
zsh
默认是没有开sh_word_split
选项的,没开的话,得到的array=("${string}")
执行
setopt
命令查看开了哪些选项,若见’shwordsplot’则开了此选项 -
多行字符串, 按换行符分割为数组, 每行开头结尾的连续空白符均保留, 连续两个换行符分割出一个
''
字符串array=() while IFS= read -r line ; do array+=("$line"); done <<< "${string}"
IFS=
等价于IFS=‘'
, 仅改变read这个命令的环境变量, 无需再恢复此环境变量
-
若不加
IFS=
, 则line的开头/结尾的连续空白符 (\s+
)会被删掉-r
:\x
不当成转义字符, 而当成普通字符串, 从而见到"\n”
不分割, 见到$'\n’
才分割.
例如
string=' asdas 12312 asdasd
’
(12312后有3个空格)
array=()
while IFS= read -r line ; do
array+=(“$line”);
done <<< “${string}”
declare -p arrayzsh输出
typeset -a array=( ’ asdas 12312 ’ ‘’ asdasd ‘’ ‘’ ‘’ )
bash输出
declare -a array=‘([0]=" asdas 12312 " [1]=“” [2]=“asdasd” [3]=“” [4]=“” [5]=“”)’
是一样的
## 字符串替换
### 引用字符串时替换
以下适用于`zsh`和`bash`
```bash
"${string/旧子串/新子串}" # 替换首个旧子串
"${string//旧子串/新子串}" # 替换所有旧子串
- 新旧字符串转义
- 空字符串:旧字符串中写作
${string/旧子串/}
, 不可写作${string/旧子串/''}
(这样会替换为''
) - 空格 新旧字符串均写作`\ ` ,不可写作
' '
- 换行 新字符串中写作
\n
,旧字符串中写作$'\n'
\
新旧字符串均写作\\
$
新旧字符串均写作\$
`
新旧字符串均写作\`
"
新旧字符串均写作\"
- 空字符串:旧字符串中写作
- 新旧字符串不转义
- 不采用正则表达式
'
新旧字符串均写作'
,不可写作\'
用sed替换
一下写法适用于mac、linux:
正则表达式进行字符串替换,-E
表示支持正则表达式
new_string="`echo $string | sed -E 's/旧子串/新字符串/'`" # 替换第一个匹配
new_string="`echo $string | sed -E 's/旧子串/新字符串/g'`" # 替换第全部匹配
输出文件内容并用正则表达式替换
cat 文件 | sed -E 's/旧子串/新字符串/' # 替换第一个匹配
sed -E 's/旧子串/新字符串/' 文件 # 替换第一个匹配
cat 文件 | sed -E 's/旧子串/新字符串/g' # 替换第全部匹配
sed -E 's/旧子串/新字符串/g' 文件 # 替换第全部匹配
-
加
-E
才能在mac和linux下都支持正则表达式,不加-E
则只linux支持正则表达式 -
新旧字符串中若有
/
,命令里应该写作\/
,不能写作"/"
,'/'
,\\\/
,/
-
支持
[a-zA-z0-9]
的写法 -
[a-z]+
:加了-E
则 应该写作[a-z]+
(不加-E
写作[a-z]\+
) -
空白字符:不支持
\s
(空白字符),支持[:space:]
,- 例如替换所有空白字符
sed -E 's/[[:space:]]+//g'
- 例如替换所有非空白字符
sed -E 's/[^[:space:]]+//g'
- 例如替换所有空白字符
-
换成换行符:
-
应该写成
's/旧子串/新字符串前段'$'\\\n''新字符串后段/g'
-
或写成
's/旧子串/新字符串前段\ 新字符串后段/g'
-
不可写成
s/旧子串/新字符串前段\n新字符串后段/g
,或s/旧子串/新字符串前段\\\n新字符串后段/g
-
-
跨行替换/将换行符换掉:如些写
sed -e ':a' -e 'N' -e '$!ba' -Ee 's/旧子串前段\n旧子串后段/新字符串/g'
-
sed的参数
-e 'xxxx'
表示追加一条命令,即- 当
sed
只执行一条命令(如s/xxx/xxx/g
), 则sed "s/xxx/xxx/g"
是sed -e "s/xxx/xxx/g"
的简写 - 当
sed
执行多条命令时,则每条命令前需要写-e
,如sed -e 'xx' -e 'xxx' -e 'xxxxx'
- 当
-
选中一行再进行替换
sed -e '/子串1/ s/字串2/字串3/' # 先选中有字串1的行,再将这些行中的字串2替换为字串3 perl -pe '/子串1/ && s/字串2/字串3/' # 先选中有字串1的行,再将这些行中的字串2替换为字串3
-
引用被匹配的内容:
sed 's/xxx\(aaa\)xxx\(bbb\)xxx/yyy\1yyy\2yyy/g'
,则替换结果为yyyaaayyybbbyyy
用perl替换
sed不支持正则表达式中的向前匹配、向后匹配、非贪婪匹配(即模式后面加?
),可以用perl命令代替sed搞替换。
Linux系统一般默认安装Perl包
以下适用于zsh
和bash
perl -pe 's/旧子串/新子串/g' <文件路径> # 输出替换后的全文
perl -i -pe 's/旧子串/新子串/g' <文件路径> # 在文件内直接替换
echo "${string}" | perl -pe 's/旧子串/新子串/g' <文件路径> # 替换输出
cat <文件路径> | perl -pe 's/旧子串/新子串/g' <文件路径> # 替换输出
取第几行
取前m行
echo "${string}" | head -n <m>
取后行
echo "${string}" | tail -n <m>
去掉前m-1行
echo "${string}" | tail -n +<m>
去掉后m行
echo "${string}" | head -n -<m>
截取
语法
写法 | 含义 |
---|---|
${variable#pattern} |
去掉variable头最短匹配的pattern |
${variable##pattern} |
去掉variable头最长匹配的pattern |
${variable%pattern} |
去掉variable尾最短匹配的pattern |
${variable%%pattern} |
去掉variable尾最长匹配的pattern |
pattern
采用通配符的语法:
?
: 0或1个任意字符*
: [0,+∞)个任意字符[...]
: 匹配一个字符 中括号里面[!...]
(bash),[\!...]
(zsh): 匹配一个字符 不在中括号里
注意:
[...]
或[!...]
后加+
*
?
均不表示对中括号描述的字符重复[1,+∞), [0,+∞), [0,1]次; 而是表示匹配完一个中括号描述的字符, 而后再匹配 一个"+”字符/[0,+∞)个任意字符/0或1个任意字符
用法
截取某个子串之前/之后
"${string%%子串*}" # 截取 string 第一个 <子串> 之前
"${string%子串*}" # 截取 string 最后一个 <子串> 之前
"${string#*子串}" # 截取 string 第一个 <子串> 之后
"${string##*子串}" # 截取 string 最后一个 <子串> 之后
注释:
-
子串
可以直接写字符, 可以是单个或多个字符, 如string='https://github.com/someone://other' echo "${string%%://*}" echo "${string#*://}"
返回
https github.com/someone://other
-
子串
可以写${substring}
string='https://github.com/someone://other' substring='://' echo "${string%%${substring}*}" echo "${string#*${substring}}"
也返回同上
-
若无目标子串, 则各自均换返回整个字符串, 例如
string='https://github.com/someone://other' substring='///' echo "${string%%${substring}*}" echo "${string#*${substring}}"
返回
https://github.com/someone://other https://github.com/someone://other
调用
bash与zsh通用的写法是 "${string}”
或 "$string"
, 加不加{...}
无影响
bash
bash中, 调用变量名为string的字符串, 必需两侧加引号.
"$string"
若不加引号, 则其中所有连续大于等于1个空白符(即正则表达式的\s+
)均替换为一个空格
且在echo时, 行开头与结尾的连续空白符(即^\s+
)会删去. 例如
a=' a b c d
e
f ' # d后有四个空格, 可能代码里显示不出来
echo $a'|'
a b c d e f |
echo '|'$a'|'
| a b c d e f |
echo "$a|"
a b c d e f |
echo "$a|"
| a b c d e f |
zsh
zsh中, 调用变量名为string的字符串, 加不加引号皆可, 均会输出完整的字符串, 各行齐全, 空白符未改动.
即 zsh中 "$string”
与 $string
结果都是 与bash中的 "$string”
相同的.
输出
以下适用于zsh
和bash
echo -E 字符串 # 输出不转义,即`\某`不会转义,输出还是`\某`
echo -e 字符串 # 输出会转义,即`\某`会转义
echo -n 字符串 # 结尾不换行
输入
参数含义:输入写到string变量
read -r string # 输入不转义,即`\某`不会转义,输入还是`\某`;输入完会换行
read -s string # 输入字符不显示;输入完不会换行
read -p 提示信息 string # bash支持,zsh不支持
组合用法:以下适用于zsh
和bash
-
输入普通信息
echo -n 提示信息; read -r string;
提示信息:输入内容 输入完进入下一行
-
输入密码
echo -n 提示信息; read -rs string; echo '';
提示信息: // 输入内容不显示 输入完进入下一行
转义
以下适用于zsh
和bash
'
in'xxxx'
:'xxx'\''xxxx'
(推荐) 或'xxx'"'"'xxx'
'xxx\'xxxx'
无用,会被解析成xxx\
(字符串),xxxx
(变量),'
(另一个字符串的开头)
"
in"xxxx"
:"xxx"\""xxxx"
或"xxx\"xxx"
(推荐)
数组
显示数组
declare -p array
返回如下
zsh
typeset -a array=( 1 '2\n' 3 '4 ' 5 )
bash
declare -a array='([0]="1" [1]="2\\n" [2]="3" [3]="4 " [4]="5")'
数组取元素
${array[@]:始:长} (从0开始)
sh/bash/zsh共享
- array数组 起始数字从 0 开始, 长度>=1; 函数的参数 起始数字从 1 开始, 长度>=1
# array数组 函数的参数
"${array[@]:起始数字:1}" "${@:起始数字:1}" # return array[起始数字]
"${array[@]:起始数字:长度}" "${@:起始数字:长度}" # return array[起始数字:长度]
"${array[@]:起始数字}" "${@:起始数字}" # return array[起始数字:]
"${array[@]}" "${@}" # return array
"${array[@]:${#array[@]}}" "${@:$#}" # return array[-1]
"${array[@]:${$((#array[@] - 1))}}" "${@:$(($# - 1))}" # return array[-2:] $(( 算式 )) 等价于 `expr 算式 `
"${array[@]:${$((#array[@] - 1))}:1}" "${@:$(($# - 1)):1}" # return array[-2:] $(( 算式 )) 等价于 `expr 算式 `
-
当
始
,长
为变量时, 必须写$
, 例如"${array[@]:$index:2}" # 可以解析 "${array[@]:index:2}" # 无法解析
-
当
始
,长
为四则运算时, 直接写算式即可, 支持+
-
*
/
(
)
, 例如
array=(zero one two three four five six seven eight nine ten )
idx=3
echo ${array[@]:(($idx+2)/2)*3-1:1}
# 返回 5
$数 (从1开始)
sh/bash/zsh共享
"$数" # 函数的第[数]个参数
-
当
数
为变量时, 只能用eval var=\"\${${idx}}\"
来获得 (如下), 或用${@:$idx:1}
(必需写$)f() { idx=2 echo $idx # 返回 2 eval var=\"\${${idx}}\" ; echo ${var} # 返回 arg 2 echo ${${idx}} # 返回 2 echo ${$idx} # 返回 f:5: bad substitution } f 'arg 1' 'arg 2'
-
当
数
为size运算算式时, 只能用eval var=\"\${$((算式))}\"
来获得 (如下), 或用${@:直接写size运算:1}
(必需写$)f() { eval var=\"\${$((1 + 1))}\" echo $var } f 'arg 1' 'arg 2' # 返回 arg 2
"${array[数]}” (bash从0开始, zsh从1开始)
- sh/bash: 数字 从0开始
- zsh: 数字从1开始
"${@[数字]}”
写法无效
"${array[数字]}" # return array[数字]
-
当
数
为变量时, 可以不写$
, 例如"${array[@]:$index:2}" # 可以解析 "${array[@]:index:2}" # 可以解析
-
当
始
,长
为四则运算时, 直接写算式即可, 支持+
-
*
/
(
)
array=(zero one two three four five six seven eight nine ten ) idx=3 echo ${array[(($idx+2)/2)*3-1]} # bash 返回 five, zsh 返回 four
${array[始,止]} (从1开始)
仅zsh支持
- 数字/起始数字 从1开始, 表示正着数
- 起始数字, 结束数字可为-1,-2, …, 表示结束数字倒着数
- 不论正着数还是倒着数, 只要起始位置比结束位置靠右, 则返回空白字符串
- 当
始
,止
为变量时, 必须写$
- 当
始
,止
为四则运算时, 直接写算式即可, 支持+
-
*
/
(
)
# array数组 函数的参数
"${array[数字]}" "${@[数字]}" # 按照上述index计法, 返回第 数字 个元素, 对应Python: array[数字-1]
"${array[起始数字,结束数字]}" "${@[起始数字,结束数字]}" # 按照上述 index 计法, 返回数组 取闭区间 [起始数字,正结束数字]
${array[起始数字,结束数字]}
对应的 python 代码
if 结束数字>0:
return array[起始数字-1:结束数字]
else if 结束数字==0:
return []
else if 结束数字==-1:
return array[起始数字-1:]
else:
return array[起始数字-1:结束数字+1]
数组长度
zsh/bash通用
${#array[@]}
zsh专用
${#array}
# 或
$#array
判断是否是数组
封装成函数
is_array() {
local array_name="$1"
# declare -p $array_name
if [[ "$(declare -p $array_name)" =~ 'declare -a ' ]] || \
[[ "$(declare -p $array_name)" =~ 'typeset -g -a ' ]] || \
[[ "$(declare -p $array_name)" =~ 'typeset -a ' ]] ; then
echo true
else
echo false
fi
}
a=(gpu{1..3})
is_array a
true
a=adsaasd
is_array a
false
注:
-
bash 返回
declare -a array_name='([0]="gpu1" [1]="gpu2" [2]="gpu3")'
-
zsh 返回
typeset -g -a array_name=( gpu1 gpu2 gpu3 ) 或 typeset -a array_name=( gpu1 gpu2 gpu3 )
数组赋值与拼接
以下适用于zsh
和bash
的
- 数组赋值
array=("${array1[@]}")
declare -p array
和declare -p array1
输出结果会完全一样,即array和array1的每个元素都一样,元素内可以含有空格、换行,均不影响赋值。
- 数组拼接并赋值
array=("${array1[@]}" "${array2[@]}")
array=("${array1[@]}" "string")
array=("string" "${array2[@]}")
array+=("${array2[@]}")
# 等价于
array=("${array[@]}" "${array2[@]}")
array1、array2元素内可以含有空格、换行,均不影响拼接和赋值。
上述不可省略"
,这是因为
-
${array[@]}$
(bash) 返回'\n'.join(array).split('\n')
(python) -
"${array[@]}$"
(zsh、bash) 和${array[@]}$
(zsh) 返回array
(python)例如:
array1=('asd' 'asd\nsads asdsa') array2=('asd') array=(${array1[@]} ${array2[@]}) declare -p array
-
bash中返回
declare -a array='([0]="asd" [1]="asd\\nsads" [2]="asdsa" [3]="asd")'
即
array
数组成员中若有换行,${array[@]}$
会得到两个数组成员,在换行号处断开成 -
zsh中
typeset -a array=( asd $'asd\nsads\nasdsa' asd )
即
array
数组成员中若有换行,${array[@]}$
不会得到两个数组成员,不在换行号处断开成
-
生成range
以下适用于zsh
和bash
一组range
array=('前缀'{起始数字..结束数字}'后缀') # 前缀、后缀可有空格、回车
原理:
- 代码
前缀{起始数字..结束数字}后缀
等价于代码'前缀xx后缀' '前缀xx后缀' …… '前缀xx后缀'
- 故
('前缀'起始数字..结束数字}'后缀')
返回数字('前缀xx后缀' '前缀xx后缀' …… '前缀xx后缀')
多组range拼接起来
array=('前缀1'{起始数字..结束数字}'后缀1' '前缀2'{起始数字..结束数字}'后缀2') # 前缀、后缀可有空格、回车
原理:
-
等价于
array=('前缀1xx后缀1' '前缀1xx后缀1' …… '前缀1xx后缀1' '前缀2xx后缀2' '前缀2xx后缀2' …… '前缀2xx后缀2')
从文件加载数组
适用于zsh、bash,mac、linux
- 以换行为分隔符(tab和空格皆不分割)
IFS_old=$IFS
IFS=$'\r\n'
array=($(<file_path))
IFS=$IFS_old
- 以空格为分隔符(tab和换行皆不分割)
IFS_old=$IFS
IFS=' '
array=($(<file_path))
IFS=$IFS_old
- 以空白字符为分割(空格、tab、换行皆分割)
array=($(<file_path))
遍历数组
遍历元素
正确写法:以下适用于zsh
和bash
和sh
# 自定义的数组
for idx in "${array[@]}"; do
...
done
# 传入函数或文件的参数
for idx in "$@"; do
...
done
# 遍历数组 (1 2 3 4)
for idx in 1 2 3 4; do
...
done
# 遍通配符路径
for idx in ./*.zip ../*; do
...
done
错误写法:
- 以下写法,
zsh
会正常遍历数组;bash
只遍历到数组首个元素就结束了遍历。
# 自定义的数组
for idx in $array; do
...
done
# 传入函数或文件的参数
for idx in $@; do
...
done
# 遍历数组 (1 2 3 4)
for idx in (1 2 3 4); do
...
done
# 遍通配符路径
for idx in $(ls ./*.zip ../*); do
# 这样会把 被匹配到的文件夹向下列出一级, 例如
# house.zip object_vox.zip object.zip room.zip ../suncg_data.zip texture.zip
# ../suncg_data:
# house object object_vox room texture
# ../suncg_data_old:
# house object object_vox room suncg_room_json suncg_room_obj texture
# ../suncg_data.zip_extract:
# house.zip object_vox.zip object.zip room.zip texture.zip
...
done
遍历编号
以下适用于zsh
和bash
和sh
for ((i = 0; i < ${#array[@]}; i++)); do
echo $i ${array[@]:$i:1}
# 不要使用 ${array[i]}, bash和sh的是i从0开始,zsh的是从1开始
done
以下适用于bash
和sh
for i in "{!array[@]}"; do
# bash和sh的是i从0开始
echo $i ${array[@]:$i:1}
# 或
echo $i ${array[i]}
done
命令返回数组
法一 for + $(命令)
或 `命令`
- 在
zsh
/bash
中返回一个字符串(不论多少行),输入给函数或for,则会自动用空格分割。 - 而不是返回数组(自带切分,而不是以空格或换行符切分)。
例:在一个文件夹下有file1
file with space
有空格的 中文文件
,
# 返回给for
for i in $(ls); do
echo "|$i|"
done
# 或返回给函数
f() {
for i in "$@"; do
echo "|$i|"
done
}
f $(ls)
在bash下返回
|file1|
| file with space|
|有空格的 中文文件|
在zsh/bash下返回
|file1|
|file|
|with|
|space|
|有空格的|
|中文文件|
-
解决办法:将
命令
结果分行返回( 如ls -1
一个文件返回一行),然后设置IFS=$'\n'
,则返回给for
或函数
的均是数组hadopt=false [ -n "$ZSH_VERSION" ] && [ "$(setopt | grep shwordsplit)" != '' ] && \ hadopt=true && setopt sh_word_split # 若为 zsh则开sh_word_split选项 OLD_IFS="$IFS"; IFS=$'\n' # --- # 返回给for for i in $(ls -1); do echo "|$i|" done # 或返回给函数 f $(ls) # --- IFS="$OLD_IFS" [ -n "$ZSH_VERSION" ] && [ "$hadopt" = false ] && unsetopt sh_word_split # 若原先没开此选项则关之
结果
> |file1|
>
> | file with space|
>
> |有空格的 中文文件|
## 数组传入传出函数
以下适用于zsh和bash
### 法一 `传入"${array[@]}"`
以下适用于zsh和bash,向文件或函数传入数组
**传入数组**
写法:
* 传入一个数组
```bash
zsh/bash 文件.sh "${array[@]}"
zsh/bash 文件.sh "$@"
func "${array[@]}"
func "$@"
- 传入多个数组(以函数为例,其余同理成立)
func "string1" "${array1[@]}" "string2" "${array2[@]}" "string3"
“${array[@]}” 必需得加引号,若不加引号,则会如下出错:
file.zsh内容为
for i in "$@"; do
echo "|$i|"
done
而在bash中运行函数
f(){ zsh file.sh $@ }
f asdas 'qwe qweasd'
会输出
|asdas|
|qwe|
|qweasd|
例子
get_array(){
local array=("$@") # 把传入变成数组
declare -p array # 检测
}
go_through_arg () {
for idx in "$@"; do
# 遍历输入,若输入有 `go_through_arg 'sad asd' 'asd' adasd`
# bash 中不支持 写作 $@,$*,"$*"
# zsh 中不支持写作 "$*",但支持写作$@,$*
# zsh和bash 仅 "$@"通用,可输出三行,分别是'sad asd','asd', 'adasd'
echo "|$idx|"
done
}
argparse () {
echo "|$1|"
echo "|$2|"
echo "|$3|"
}
array=("a" "b asd" "c\nasd
asdsa")
传参写法
get_array "${array[@]}" # 传入数组
typeset -a array=( a 'b asd' 'c\nasd\nasdsa' )
go_through_arg "${array[@]}" # 传入数组
|a| |b asd| |c asd asdsa|
argparse "${array[@]}" # 传入数组
|a| |b asd| |c asd asdsa|
法二 用declare -p array
作为表示
以下适用于zsh和bash
缺点:依赖于zsh的版本的操作系统
传出数组
make_array(){
local array
declare -a array
array=('a' 'b asd' 'c\nasd\nasds
asdas')
echo -E ${"$(declare -p array )"#*=} #
}
make_array
mac zsh 5.7.1 (x86_64-apple-darwin18.2.0) 返回,不能使用本法
( a 'b asd' 'c\nasd\nasdsa' )
Linux zsh 5.4.2 (x86_64-ubuntu-linux-gnu)返回 ,能使用本法
( a 'b asd' $'c\\nasd\\nasds\n asdas' )
Linux zsh 5.0.5 (x86_64-pc-linux-gnu) 返回 ,能使用本法
(a 'b asd' 'c\nasd\nasds asdas')
传入数组
get_array(){
local getton_array
declare -a getton_array
eval "getton_array="$1 # get array
declare -p getton_array
}
get_array "$(make_array)"
mac zsh 5.7.1 (x86_64-apple-darwin18.2.0) 返回,不能使用本法
typeset -a getton_array=( a 'b asd' $'c\nasd\nasds\n asdas' )
Linux zsh 5.4.2 (x86_64-ubuntu-linux-gnu)返回 ,能使用本法
typeset -a getton_array=( a 'b asd' $'c\\nasd\\nasds\n asdas' )
Linux zsh 5.0.5 (x86_64-pc-linux-gnu) 返回 ,能使用本法
typeset -a getton_array getton_array=(a 'b asd' 'c\nasd\nasds asdas')
法三 修改分隔符
以下适用于zsh和bash
a2s() {
local string=''
if [ $# -ne 0 ]; then
string="$1"
shift
fi
for i in "$@"; do
string="$string䴅$i" # 可以用任意字符,但只能是单个字符,故选用冷门汉字
done
echo -E "$string"
}
make_array() {
local array
declare -a array
array=('a' 'b asd' 'c\nasd\nasdss
asdas')
a2s "${array[@]}"
# echo -E "$(a2s \"${array[@]}\")"
}
make_array
返回
a䴅b asd䴅c\nasd\nasdss asdas
get_array(){
local str="$1"
local OLD_IFS=$IFS
IFS='䴅'
local array=($(echo -E "$str"))
# local array=($str)
IFS=$OLD_IFS
declare -p array
}
get_array "$(make_array)"
返回
typeset -a array array=( 'c\nasd\nasdss asdas' )
引用传参——在函数中修改参数
适用于 zsh 与 bash
f() {
local a_="$1"
eval $a_='asdsad' # 注意,这里的`a_`不要和传入`f`的值`a`重名
}
# 或
f() {
eval $1='asdsad'
}
# 或
f() {
local a_="$1"
local c='asdsad'
eval $a_=\"\$c\"
}
# 或
f() {
local c='asdsad'
eval $1=\"\$c\"
}
# 而后
test() {
local a=123
f a # 注意,这里的`a`不要和`f`中的`a_`重名
echo $a
}
test
asdsad
其原理是eval $a_=\"\$c\"
等价于执行了a="$c"
运行数组
错误的做法
-
方法一
eval "${array[@]}" # 即等价于 python 中的 sys.command(' '.join(array))
- 这样会把含有空白字符的字符串拆开
-
方法二
"${array[@]}"
- 当array的第一个元素不是终端的命令时(如第一个元素为
CUDA_VISIBLE_DEVICES=1
),则无法执行 - 当array中有元素等于
|
、;
、{
,}
&&
||
等时,也无法执行
- 当array的第一个元素不是终端的命令时(如第一个元素为
正确的做法
本方法可以克服上述法一、二的所有问题
evalarray() {
if [ $# -eq 1 ]; then
# 若array仅一个元素,则直接 sys.command(array[0])
eval "$1"
else
# 否则 sys.command( ' '.join([
# x.replace("'", "'\''")
# if ' ' in x or '\n' in x or '\t' in x
# else x
# for x in array]
# ) )
local processedarray=()
for arg in "$@"; do
if [[ "$arg" =~ ' ' ]] || [[ "$arg" =~ $'\t' ]] || [[ "$arg" =~ $'\n' ]]; then
# 若arg中有空白字符,则arg两端加',arg中间的'改为'\''
processedarray+=("'${arg//'/'\\''}'")
else
processedarray+=("${arg}")
fi
done
eval "$processedarray[@]"
fi
}
使用
evalarray "${array[@]}"
例如:
- 用法一:所有命令和参数均分开写。当参数含有空白字符和特殊运算连接符(
|
、;
、{
,}
&&
||
等)时,才用括号括起来。
array=(touch '文件名 有空格' '&&' ls .. '|' grep a)
evalarray "${array[@]}"
# 或
evalarray touch '文件名 有空格' '&&' ls .. '|' grep a
evalarray touch 文件名\ 有空格 '&&' ls .. '|' grep a
- 用法二:所有命令和参数写在一个字符串中
array=('touch 文件名\ 有空格 && ls .. | grep a')
evalarray "${array[@]}"
# 或
evalarray 'touch 文件名\ 有空格 && ls .. | grep a'
- 注意,用法一二不能混着写,例如,
evalarray touch '文件名 有空格' '&&' ls .. '|' 'grep a'
这样不行,运行时会把’grep a’当成一个终端命令,故而报错
(eval):1: command not found: grep a
输出表格
column
命令
代码块 | column -t -s 分割字符的集合
即能输出表格, 使得各列左对齐
-
例1: 单个分隔字符:
注意必需写成
$'\t’
才会转义成制表符, 用它分隔列; 写成"\t
”'\t’
均不转义, 而是用\
与t
这两个字符分列echo 'Name\tScore -----\t----- Somebody with a very long name\t100 ShortNmae\t1231981738184788381413131 Ann\t \tnana' | column -t -s $'\t'
输出
Name Score ----- ----- Somebody with a very long name 100 ShortNmae 1231981738184788381413131 Ann nana
-
例2: 多个分隔字符
echo 'Name|Score\n-----|----- Somebody with a very long name\t100 ShortNmae\t1231981738184788381413131 Ann| |nana' | column -t -s $'\t|'
输出同例1
sudo执行
sudo执行非sudo的函数
sudo bash -c "$(declare -f <非sodu的函数名>); <非sodu的函数名>"
sudo执行代码段
sudo bash <<EOF
代码段
EOF
sudo执行文件
sudo bash <文件路径>