Pure Bash Bible–外部进程的纯Bash替代方案的集合

2022-02-17 16:15:30

本书的目标是记录只使用内置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

......