本书的目标是记录只使用内置bash特性来完成各种任务的常见方法和鲜为人知的方法。使用本《圣经》中的片段有助于从脚本中删除不必要的依赖项,并且在大多数情况下使它们更快。我在开发neofetch、pxltrm和其他较小的项目时遇到了这些技巧,并发现了一些。
下面的代码片段是使用shellcheck编写的,并在适用的情况下编写了测试。想要贡献吗?阅读投稿。它概述了单元测试是如何工作的,以及在圣经中添加代码片段时需要做什么。
看到一些描述错误的东西了吗,有问题还是完全错了?打开问题或发送请求。如果圣经遗漏了什么,打开一个问题,就会找到解决办法。
纯bash替代外部进程和程序的集合。bash脚本语言比人们意识到的强大,大多数任务都可以在不依赖外部程序的情况下完成。
在bash中调用外部进程代价高昂,过度使用会导致明显的速度减慢。使用内置方法(如果适用)编写的脚本和程序将更快,需要更少的依赖性,并更好地理解语言本身。
本书的内容为解决在bash中编写程序和脚本时遇到的问题提供了参考。函数格式的示例展示了如何将这些解决方案合并到代码中。
这是sed、awk、perl和其他工具的替代品。下面的函数通过查找所有前导和尾随空格,并将其从字符串的开头和结尾移动来工作。:内置变量用于替代临时变量。
trim#string(){#用法:trim#string";示例string";:";${1#";${1%[![:space:]*}";}" : " ${{uz%";${u##*[![:空格:]}";}" printf';%s\n和#39" $_ "}
这是sed、awk、perl和其他工具的替代品。下面的函数通过滥用分词来创建一个没有前导/尾随空格和截断空格的新字符串。
#shellcheck disable=SC2086,SC2048 trim#all(){用法:trim#all";示例字符串";set-f set--$*printf';%s\n';";$*";set+f}
$trim_all";你好,世界";你好,World$name=";约翰·布莱克是我的名字"$ 修剪全部"$姓名";约翰·布莱克是我的名字。
bash的结果';s regex匹配可以用于替换大量用例中的sed。
警告:这是少数依赖于平台的bash特性之一。bash将使用用户安装的任何regex引擎';s系统。如果以兼容性为目标,请坚持使用POSIX正则表达式功能。
注意:本例仅打印第一个匹配组。当使用多个捕获组时,需要进行一些修改。
regex(){#用法:regex";string";";regex";[$1=~$2]]和printf';%s\n';";${BASH#u重新匹配[1]";}
$#修剪前导空白。$正则表达式';你好''^\s*(*)和#39;你好$#验证十六进制颜色。$正则表达式"#FFFFFF"'^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'#FFFFFF$#验证十六进制颜色(无效)。$正则表达式";红色和#34'^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$';#无输出(无效)
是_hex_color(){if[$1=~^(#?([a-fA-F0-9]{6}}|[a-fA-F0-9]{3}])$];然后printf';%s\n和#39" ${BASH_重赛[1]}";else printf';%s\n和#39"错误:$1是无效颜色" 返回1 fi}read-r coloris_hex_color"$颜色";|124;颜色="#FFFFFF";#做事。
split(){#用法:split";string";delimiter";IFS=$';\n';read-d";&ra arr<;<;<;<;>;&34;$1/$2/$&39;\n';]" printf';%s\n和#39" ${arr[@]}";}
$split和#34;苹果、橙子、梨、葡萄和#34", "AppleSRangeSpearsgraps$split和#34;1, 2, 3, 4, 5 " ", "12345#多字符分隔符也有效!$分裂和#34;你好---世界---我的---名字---是---约翰""--- "我的名字是约翰
$strip#all";敏捷的棕色狐狸""[aeiou]和#34;Th Qck Brwn外汇$strip#u all和#34;敏捷的棕色狐狸""[:空格:][]和#34;QuickBrownFox$strip#all";敏捷的棕色狐狸""快";棕色狐狸
$strip";敏捷的棕色狐狸""[aeiou]和#34;第四快棕色狐狸$strip";敏捷的棕色狐狸""[:空格:][]和#34;敏捷的棕色狐狸
urlencode(){#用法:urlencode";string";local LC#u ALL=C for((i=0;i<;${#1};i++);do:"${1:i:1}";案例";$#" 在[a-zA-Z0-9.~-])printf和#39;%s'" $_ " ;; *) printf';%%02X和#39"' $_ " ;; esac完成了打印F和#39;\n';}
如果[[$var==*sub_string*];然后printf';%s\n和#39"子字符串在变量和34中;fi#逆(子字符串不在字符串中)。如果[$var!=*sub_string*];然后printf';%s\n和#39"子#u字符串不在变量"中;fi#这也适用于阵列!如果[${arr[*]}==*子字符串*];然后printf';%s\n和#39"子字符串在数组中" 菲
34例$var";在*sub_string*)#做事*sub_string2*)#做更多的事;*)#其他的以撒
如果[[$var==sub_string*];然后printf';%s\n和#39"var以sub_字符串开头" fi#逆(var不以sub#u字符串开头)。如果[[$var!=sub_string*];然后printf';%s\n和#39"var不以sub_字符串开头" 菲
如果[$var==*sub_string]];然后printf';%s\n和#39"var以sub_字符串结尾" fi#逆(var不以sub_字符串结尾)。如果[$var!=*子字符串]];然后printf';%s\n和#39"var不以sub_字符串结尾" 菲
启用extdebug允许访问BASH_ARGV数组,该数组反向存储当前函数的参数。
reverse#array(){#用法:reverse#array";array";shopt-s extdebug f()(printf';%s\n';";${BASH#ARGV[@]";);f";$@" shopt-u extdebug}
创建临时关联数组。当设置关联ArrayValue并发生重复赋值时,bash会覆盖该键。这使我们能够有效地删除重复的阵列。
remove_array_dups(){#用法:remove_array_dups";array";declare-tmp_array for i in";$@";do[$i]]和IFS=";";tmp#u array[=1完成打印F';%s\n和#39" ${!tmp_数组[@]}";}
$remove_array_dups 1 1 2 2 3 3 3 3 3 4 4 5 5 5 5 512345$arr=(红-红-绿-蓝)$remove_array_dups和#34${arr[@]}";红绿蓝
$array=(红绿蓝黄棕色)$random_array_element"${array[@]}";黄色#也可以传递多个参数。$随机数组元素12345673
每次调用printf时,都会打印下一个数组元素。当打印到达最后一个数组元素时,它会再次从第一个元素开始。
arr=(苹果橙子番茄)#元素和索引。因为我在"${!啊[@]}";打印F';%s\n和#39" ${arr[i]}";完成#替代方法。对于((i=0;i<;${#arr[@]};i++);打印F';%s\n和#39" ${arr[i]}";完成
#贪婪的例子。存档*;打印F';%s\n和#39" $文件";完成#在dir中保存PNG文件。用于~/Pictures/*中的文件。巴布亚新几内亚;打印F';%s\n和#39" $文件";完成#遍历目录。对于~/Downloads/*/中的dir;打印F';%s\n和#39" $署长";完成#支架扩展。对于/path/to/parentdir/{file1,file2,subdir/file3}中的文件;打印F';%s\n和#39" $文件";完成#递归迭代。shopt-s globstar用于~/Pictures/***中的文件;打印F';%s\n和#39" $文件";完成shopt-u globstar
#Bash<;4(丢弃空行)。IFS=$和#39;\n';阅读-d"" -ra文件_数据<"文件";#Bash<;4(保留空行)。而read-r行;do file_data+=(";$line";)完成<"文件";#Bash 4+mapfile-t file_data<"文件";
head(){用法:head";n";file";mapfile-tn";$1";line<;";$2";printf';%s\n';"$#line[@]
tail(){用法:tail";n";";file";mapfile-tn 0行<;";$2";printf';%s\n';";${line[@]:$1";]
$tail 2~/。bashrc#启用tmux。#[-z";$TMUX";]]&&;exec tmux$tail 1~/。bashrc#[-z";$TMUX";]]&&;执行官tmux
lines(){用法:lines";file";mapfile-tn 0 lines<;";$1";printf';%s\n';";${#lines[@]}";}
该方法使用的内存比mapfile方法少,在bash 3中也可以使用,但对于较大的文件,它的速度较慢。
lines_loop(){用法:lines_loop";file";count=0,而IFS=read-r#do((count++)done<;";$1";printf';%s\n';";$count 34;}
其工作原理是将glob的输出传递给函数,然后计算参数的数量。
#计算目录中的所有文件。$count~/Downloads/*232#计算目录中的所有目录。$count~/Downloads/*/45#计算目录中的所有jpg文件。$计数~/Pictures/*。jpg64
extract(){用法:extract file";opening marker";";closing marker";而IFS=$';\n';read-r line;do[$extract&;$line!=";$3";]&&;printf';%s\n和#39" $第34行;[$line==";$2";]&&;摘录=1[$line==";$3";]]&&;提取=完成<" $1 "}
dirname(){#用法:dirname";path";local tmp=${1:-.}[[$tmp!=*[!/]*]&&;{printf';/\n';return}tmp=${tmp%%";${tmp###*[!/]}";}[$tmp!=*/*]&&;{printf';\n';return}tmp=${tmp%/*}tmp=${tmp%";${tmp###*[!/]}";}printf';%s\n和#39" ${tmp:-/}";}
basename(){用法:basename";path";[";后缀";]本地tmp tmp=${1%";${1##*[!/]}";}tmp=${tmp##*/}tmp=${tmp%";${2/";$tmp";}"} printf';%s\n和#39" ${tmp:-/}";}
$hello_world=";价值";#创建变量名。$var=";世界";美元ref=";你好,$var";#打印存储在'中的变量名的值;你好$var';$printf';%s\n和#39" ${!ref}";价值
与普遍的看法相反,利用原始逃逸序列没有问题。使用tput提取与手动打印相同的ANSI序列。更糟糕的是,tput实际上并不便携。有很多tput变体,每个都有不同的命令和语法(在FreeBSD系统上尝试tput setaf 3)。原始序列很好。
注意:在下面任何代码前加上2,将其变成';s效果关闭(示例:21=粗体文本关闭,22=模糊文本关闭,23=斜体文本关闭)。
展开到以VAR开头的变量名的IFS分隔列表。如果双引号,每个变量名将展开到一个单独的单词。
从N个字符到N个字符获取子字符串。(${VAR:10:10}:从char 10到char 20获取子字符串)
#语法:{<;开始>;.<;结束>;}打印数字1-100。echo{1..100}#打印浮动范围。回声1。{1..9}#打印字符a-z.echo{a..z}echo{a..z}#嵌套。echo{A..Z}{0..9}#打印零填充数字。#警告:bash4+echo{01..100}#更改增量金额。#语法:{<;开始>;.<;结束>;<;递增>;}警告:bash4+echo{1..10..2}#增加2。
如果文件比文件2新(使用修改时间),或者文件存在而文件2不存在。
如果文件早于文件2(使用修改时间),或者文件2存在而文件不存在。
#如果var2大于var,则将var的值设置为var2。#var:要设置的变量。#var2>;var:要测试的条件。#?var2:如果测试成功。#如果测试失败。((var=var 2>;var?var 2:var))
陷阱允许脚本在各种信号上执行代码。在pxltrm(一个用bash编写的像素艺术编辑器)中,陷阱用于在调整窗口大小时重新绘制用户界面。另一个用例是在脚本退出时清理临时文件。
应该在脚本开始附近添加陷阱,以便捕获任何早期错误。
如果不需要unicode,可以禁用它以提高性能。结果可能会有所不同,但是neofetch和其他程序已经有了明显的改进。
注意:有时人们可能有充分的理由使用#/bin/bash或二进制文件的另一个直接路径。
" $主机名";#注意:此变量可能为空。#(可选)将回退设置为hostname命令" ${HOSTNAME:-$(HOSTNAME)}";
这可以用于为不同的操作系统添加条件支持,而无需调用uname。
每次使用$RANDOM时,都会返回一个介于0和32767之间的不同整数。此变量不应用于任何与安全性相关的内容(包括加密密钥等)。
在纯bash和stty/tput中编写脚本时,这很方便。
get_term_size(){#用法:get_term_size#(::)是一个微睡眠,以确保变量立即导出。shopt-s checkwinsize;(:::)printf';%s\n';";$LINES$COLUMNS";}
用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:用法:获取窗户尺寸图尺寸印刷F和39;%b和39;b和[35;39;b和[35;34;b和39;b和39;阅读-t-t-t-t 0.05-sra术语t t t-t-t 0.05-sra术语t-t-0.05-sra术语(sra术语)t-t-t-sra术语)t-t-t-t-0.05-sra术语(sra术语)t-t-t-t-t)t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-t-#34;}
get_cursor_pos(){#用法:get_cursor_pos IFS=';[#';read-p$';\e[6n';-dr-rs y x#printf 39;%s\n';";$x$y";]
hex_to_rgb(){用法:hex_to_rgb";#FFFFFF";#hex_to_rgb";000000";:";${1/\\\\}34;(r=16#${0:2},g=16#${2:2},b=16#${4:2})printf';%s\n和#39" $r$g$b";}
#小C风格。对于(;i++<;10;)){echo";$i";}未记录的方法。因为{1..10}中的i;{echo";$i";}膨胀因为{1..10}中的i;做回声"$我";完成#C风格。(i=0;i<;=10;i++);做回声"$我";完成
#正规方法f(){echo hi;}#使用子shell f()(echo hi)#使用算术#这可以用来分配整数值。#示例:fa=1 35; fa++f()($1))#使用测试、循环等#注意:'while'、'until'、'case'、'(())'、'[[])也可以使用。f()如果为真;然后是回声"$1 " ; fi f()代表i in";$@"; 做回声"$我";完成
#一行#注意:第三条语句可能会在第一条语句为真时运行[$var==hello]]&&;echo hi | | echo bye[$var==你好]]&&;{echo hi;echo那里;}||echo bye#多行(无其他,单语句)#注意:退出状态可能与if语句[[$var==hello]]不同&&;echo hi#多行(无其他)[$var==hello]]&&;{echo hi#..}
:内置可用于避免在case语句中重复变量=。$变量存储最后一个命令的最后一个参数:始终成功,以便可以使用它存储变量值。
#修改了Neofetch中的代码片段。34例$OSTYPE";在";达尔文";*):"马科斯""linux";*):"Linux";;*"bsd";*|"蜻蜓";|"比特瑞格(bitrig";):"BSD""西格温";|"msys";|"win32";):"窗户";;*)printf';%s\n和#39"检测到未知操作系统,正在中止" >&;2.出口1;;esac#最后,设置变量。os=";$\U"
read#u sleep(){#用法:read#u sleep 1#read#u sleep 0.2 read-rt";$1";<;>;<;(:)|:}
对于性能关键的情况,如果打开和关闭过多的文件描述符是不经济的,那么对于所有读取调用,文件描述符的分配只能进行一次:
执行{sleep_fd}<>&书信电报;(:)而一些快速测试;do#相当于sleep 0.001 read-t0.001-u$sleep#u fd done
#有三种方法可以做到这一点,任何一种都可以使用。类型-p可执行文件名称&>/dev/null哈希可执行文件\u name&>/dev/null命令-v可执行文件名称&>/dev/null#作为测试。如果类型为-p可执行文件名称&>/dev/null;然后#程序进入路径。fi#逆。如果类型-p可执行文件名称&>/dev/null;那么#程序不在路径中。fi#示例(如果未安装程序,请提前退出)。如果类型-p转换&>/dev/null;然后printf';%s\n和#39"错误:未安装转换,正在退出" 1号出口
Bash的printf有一个获取日期的内置方法,可以用来代替date命令。
date(){用法:date";format";#参见:';man strftime';获取format.printf";($1)T\\n";";-1";}
#使用上述功能。$日期";%a%d%b-%l:%M%p";6月15日星期五上午10:00#直接使用printf。$printf';%(%a%d%b-%l:%M%p)T\n和#39"-1 "6月15日星期五上午10:00#使用printf分配变量。$printf-v日期和#39;%(%a%d%b-%l:%M%p)T\n和#39'-1 '$ printf';%s\n和#39" $日期";6月15日星期五上午10:00
$:\\u#展开参数,就像它是一个提示字符串一样。$printf';%s\n和#39" ${@P}";黑色
(N=0;N<;16;N<;16;N;N<;16;N;N<;16;N;N;N<;16;N+N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N)N(N)34;N)34;N(N(N(N(N(N(N)N;N)N(N)N;N)N;N;N)N;N;N;N;N;N)N)N)N)N;N)N)N)N)N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N)34;N)%${#C}:1}"" $((B%16))和#34;;3 | 5 | 7 | 9)打印和#39;%02x-#39" $B";;*)printf';%02x和#39" $B";;esac完成了打印F和#39;\n';}
这是一种绘制进度条的简单方法,无需函数本身中的for循环。
bar(){#用法:bar 1 10#^--经过的百分比(0-100)。#^--以字符为单位的总长度。((经过的=$1*$2/100))#用空格创建条形图;printf-v总计和#34;%$(($2-已逝))s和#34;printf';%s\r'"[${prog//-}${total}]";}
(i=0;i<;=100;i++);do#Pure bash micro sleeps(例如)。(:;:)&&;(:;:)&&;(:;:)&&;(:;:)&&;打印条。酒吧"$我""10 " 完成打印F和#39;\n';
get#u functions(){#用法:get#u functions IFS=$';\n';read-d";";ra functions<;;(declare-F)printf';%s\n';";${functions[@]//declare-F}";}
这将运行给定的命令并保持其运行,即使在终端或SSH连接终止后也是如此。所有输出都被忽略。
bkr(){(nohup";$@";&;>;/dev/null&;)}bkr/一些剧本。sh#一些脚本。sh现在正在后台运行
它使用本地namerefs来避免使用var=$(some_func)样式的命令替换函数输出captu
......