-
Notifications
You must be signed in to change notification settings - Fork 80
/
README.md.bak
2168 lines (1591 loc) · 46.1 KB
/
README.md.bak
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<p align="center"><img src="https://raw.githubusercontent.com/odb/official-bash-logo/master/assets/Logos/Icons/PNG/512x512.png" width="200px"></p>
<h1 align="center">pure bash bible</h1> <p
align="center">bash奇巧淫技.</p>
<p align="center">
<a href="./LICENSE.md"><img
src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
</p>
<br>
<a href="https://leanpub.com/bash/">
<img src="https://s3.amazonaws.com/titlepages.leanpub.com/bash/hero" width="40%" align="right">
</a>
本书原作者将书中的内容发布到了[github](https://github.com/dylanaraps/pure-bash-bible)上,我仅仅是将其翻译为中文,并解释了其中的部分语句语法,希望可以对今后的工作有所帮助。
# 以下是翻译后的原文 #
这本书的目的是汇总只使用内置`bash`的特性来实现总所周知和鲜为人知的各项任务。 使用此参考书中的代码段可以帮助你从脚本中删除不需要的依赖项,并且在大多数情况下可以使它们运行的更快。 我偶然碰到了这些技巧并在开发[neofetch](https://github.com/dylanaraps/neofetch), [pxltrm](https://github.com/dylanaraps/pxltrm) 和一些其他小的项目的时候发现了一些别的技巧。
下面的片段使用`shellcheck`进行了检查,并将测试写在了适用的地方。 想要贡献自己的代码? 阅读 [CONTRIBUTING-Zh_CN.md](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/CONTRIBUTING-Zh_CN.md). 它概述了向参考书中增加片段时,单元测试的工作方式以及其他所需的内容。
看到了一些东西描述是不准确的、有缺陷的更或者是完全错误的?那么请新建一个issue或者发送一个pull request.如果参考书中缺少某些你想要的事物,也请新建一个issue并给出你能找到的解决方法。
<br>
# 目 录
<!-- vim-markdown-toc GFM -->
* [前言](#前言)
* [字符串](#字符串)
* [删除字符串前后空格](#删除字符串前后空格)
* [删除字符串中的所有的空白并用空格分割单词](#删除字符串中的所有的空白并用空格分割单词)
* [在字符串上匹配正则表达式](#在字符串上匹配正则表达式)
* [指定分隔符拆分字符串](#指定分隔符拆分字符串)
* [将字符串转换为小写](#将字符串转换为小写)
* [将字符串转换为大写](#将字符串转换为大写)
* [反转字符串大小写](#反转字符串大小写)
* [删除字符串中的引号](#删除字符串中的引号)
* [从字符串中删除所有正则实例](#从字符串中删除所有正则实例)
* [从字符串中删除第一次出现的正则实例](#从字符串中删除第一次出现的正则实例)
* [在字符串开头匹配正则并删除](#在字符串开头匹配正则并删除)
* [在字符串末尾匹配正则并删除](#在字符串末尾匹配正则并删除)
* [百分号编码字符串](#百分号编码字符串)
* [解码用百分比编码的字符串](#解码用百分比编码的字符串)
* [检查字符串是否包含子字符串](#检查字符串是否包含子字符串)
* [检查字符串是否以子字符串开头](#检查字符串是否以子字符串开头)
* [检查字符串是否以子字符串结尾](#检查字符串是否以子字符串结尾)
* [数组](#数组)
* [反转数组](#反转数组)
* [删除重复的数组元素](#删除重复的数组元素)
* [随机返回一个数组元素](#随机返回一个数组元素)
* [循环迭代一个数组](#循环迭代一个数组)
* [在两个值之间转换](#在两个值之间转换)
* [循环](#循环)
* [循环生成范围内的数字](#循环生成范围内的数字)
* [循环遍历可变数字范围](#循环遍历可变数字范围)
* [循环数组](#循环数组)
* [循环输出带索引的数组](#循环输出带索引的数组)
* [循环遍历文件的内容](#循环遍历文件的内容)
* [循环遍历文件和目录](#循环遍历文件和目录)
* [文本处理](#文本处理)
* [读取文件到一个字符串中](#读取文件到一个字符串中)
* [读取文件到一个数组中 (*按行读取*)](#读取文件到一个数组中)
* [获取文件的前N行](#获取文件的前N行)
* [获取文件的最后N行](#获取文件的最后N行)
* [获取文件中的行数](#获取文件中的行数)
* [计算目录中的文件或目录](#计算目录中的文件或目录)
* [创建一个空文件](#创建一个空文件)
* [提取两个标记之间的行](#提取两个标记之间的行)
* [文件路径](#文件路径)
* [获取文件路径的目录名](#获取文件路径的目录名)
* [获取文件路径的基本名称](#获取文件路径的基本名称)
* [变量](#变量)
* [分配和访问一个变量](#分配和访问一个变量)
* [根据另一个变量命名变量](#根据另一个变量命名变量)
* [转义字符](#转义字符)
* [文本颜色](#文本颜色)
* [文本属性](#文本属性)
* [移动光标](#移动光标)
* [删除文本](#删除文本)
* [参数拓展](#参数拓展)
* [间接](#间接)
* [替换](#替换)
* [长度](#长度)
* [扩展](#扩展)
* [改变大小写](#改变大小写)
* [默认值](#默认值)
* [花括号展开](#花括号展开)
* [范围](#范围)
* [字符串列表](#字符串列表)
* [条件表达式](#条件表达式)
* [文件判断](#文件判断)
* [文件比较](#文件比较)
* [条件变量](#条件变量)
* [比较变量](#比较变量)
* [算术运算符](#算术运算符)
* [指派](#指派)
* [四则运算](#四则运算)
* [位运算](#位运算)
* [逻辑运算](#逻辑运算)
* [复杂运算](#复杂运算)
* [算术运算符-1](#算术运算符-1)
* [用更简单语法设置变量](#用更简单语法设置变量)
* [三元测试](#三元测试)
* [trap命令](#trap命令)
* [在脚本退出时执行操作](#在脚本退出时执行操作)
* [忽略终端中断(CTRL+C,SIGINT)](#忽略终端中断)
* [对窗口调整大小时做出反应](#对窗口调整大小时做出反应)
* [在命令之前执行某些操作](#在命令之前执行某些操作)
* [在shell函数或源文件完成执行时执行某些操作](#在shell函数或源文件完成执行时执行某些操作)
* [性能](#性能)
* [禁用Unicode码](#禁用Unicode码)
* [已过时的语法](#已过时的语法)
* [释伴声明](#释伴声明)
* [命令替换](#命令替换)
* [声明函数](#声明函数)
* [内部变量](#内部变量)
* [获取`bash`二进制文件的位置](#获取`bash`二进制文件的位置)
* [获取当前运行`bash`命令的版本](#获取当前运行`bash`命令的版本)
* [打开用户默认的文本编辑器](#打开用户默认的文本编辑器)
* [获取当前函数的名称](#获取当前函数的名称)
* [获取系统的主机名](#获取系统的主机名)
* [获取操作系统的架构(32位或64位)](#获取操作系统的架构)
* [获取操作系统/内核的名称](#获取操作系统/内核的名称)
* [获取当前的工作目录](#获取当前的工作目录)
* [获取脚本运行的秒数](#获取脚本运行的秒数)
* [获取伪随机整数](#获取伪随机整数)
* [有关终端的信息](#有关终端的信息)
* [获取终端的总行列数(*来自脚本*)](#获取终端的总行列数)
* [获取终端的像素大小](#获取终端的像素大小)
* [获取当前光标位置](#获取当前光标位置)
* [转换](#转换)
* [将十六进制颜色转换为RGB](#将十六进制颜色转换为RGB)
* [将RGB颜色转换为十六进制](#将RGB颜色转换为十六进制)
* [代码高尔夫](#代码高尔夫)
* [更短的`for`循环语法](#更短的`for`循环语法)
* [更短的无限循环](#更短的无限循环)
* [更短的函数声明](#更短的函数声明)
* [更短的`if`语法](#更短的`if`语法)
* [用`case`语句来更简单的设置变量](#用`case`语句来更简单的设置变量)
* [其他](#其他)
* [使用`read`作为`sleep`命令的替代品](#使用`read`作为`sleep`命令的替代品)
* [检查一个命令是否在用户的PATH中](#检查一个命令是否在用户的PATH中)
* [使用`strftime`获取当前日期](#使用`strftime`获取当前日期)
* [获取当前用户的用户名](#获取当前用户的用户名)
* [生成一个V4版本的UUID](#生成一个V4版本的UUID)
* [进度条](#进度条)
* [获取脚本中的函数列表](#获取脚本中的函数列表)
* [绕过shell别名](#绕过shell别名)
* [绕过shell函数](#绕过shell函数)
* [在后台运行命令](#在后台运行命令)
* [后记](#后记)
<!-- vim-markdown-toc -->
<br>
<!-- CHAPTER START -->
# 前言
纯`bash`脚本替代外部流程和程序的集合。 `bash`脚本语言远比大部分人了解到的更强大,大多数任务都可以在不依赖外部程序的情况下由`bash`独立完成。
在`bash`中调用外部进程是昂贵的,过度使用会导致效率明显的下降。 使用内置方法编写的脚本和程序(*在适合的地方*)将更快,依赖性更小,并能对脚本本身有更好的理解。
本书的目的是为大家用`bash`编写程序和脚本过程中遇到问题时提供了一种思路。 示例展示了将这些解决方案合并到代码中的函数格式。
<!-- CHAPTER END -->
<!-- CHAPTER START -->
# 字符串
## 删除字符串前后空格
这是`sed`,`awk`,`perl`和其他工具的替代品。下面的函数通过查找所有头尾空格并在字符串的开头和结尾删除它来实现这一功能。
`:`内置用于代替临时变量。
**示例函数:**
```sh
trim_string() {
# Usage: trim_string " example string "
: "${1#"${1%%[![:space:]]*}"}"
: "${_%"${_##*[![:space:]]}"}"
printf '%s\n' "$_"
}
```
**用法示例:**
```shell
$ trim_string " Hello, World "
Hello, World
$ name=" John Black "
$ trim_string "$name"
John Black
```
## 删除字符串中的所有的空白并用空格分割单词
这是`sed`,`awk`,`perl`和其他工具的替代品。
下面的函数通过重复使用单词拆分来创建一个没有前导/尾随空格的新字符串,并用空格分割字符串中的单词。
**示例函数:**
```sh
# shellcheck disable=SC2086,SC2048
trim_all() {
# Usage: trim_all " example string "
set -f
set -- $*
printf '%s\n' "$*"
set +f
}
```
**用法示例:**
```shell
$ trim_all " Hello, World "
Hello, World
$ name=" John Black is my name. "
$ trim_all "$name"
John Black is my name.
```
## 在字符串上匹配正则表达式
对于使用`sed`的大部分情况,`bash`的正则表达式匹配结果完全可以替代。
**警告**: 这是少数平台相关的“bash”功能之一。
`bash`将使用用户系统上安装的任何正则表达式引擎。
如果要兼容,请坚持使用符合POSIX规范的正则表达式引擎。
绝大部分发行版Linux中的bash均实现了POSIX规范。
**警告**: 此示例仅打印第一个匹配组。 使用时多个匹配组需要进行一些修改。
**示例函数:**
```sh
regex() {
# Usage: regex "string" "regex"
[[ $1 =~ $2 ]] && printf '%s\n' "${BASH_REMATCH[1]}"
}
```
**用法示例:**
```shell
$ # 删除开头的空白字符.
$ regex ' hello' '^\s*(.*)'
hello
$ # 验证十六进制颜色.
$ regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
#FFFFFF
$ # 验证十六进制颜色(无效).
$ regex "red" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
# no output (invalid)
```
**在脚本中的示例:**
```shell
is_hex_color() {
if [[ $1 =~ ^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$ ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"
else
printf '%s\n' "error: $1 is an invalid color."
return 1
fi
}
read -r color
is_hex_color "$color" || color="#FFFFFF"
# Do stuff.
```
## 指定分隔符拆分字符串
**警告:** 需要`bash` 4+以上的版本
这是`cut`,`awk`和其他工具的替代品。
**示例函数:**
```sh
split() {
# Usage: split "string" "delimiter"
IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}"
printf '%s\n' "${arr[@]}"
}
```
**示例用法:**
```shell
$ split "apples,oranges,pears,grapes" ","
apples
oranges
pears
grapes
$ split "1, 2, 3, 4, 5" ", "
1
2
3
4
5
# 多字符分隔符也可以工作!
$ split "hello---world---my---name---is---john" "---"
hello
world
my
name
is
john
```
## 将字符串转换为小写
**警告:** 需要`bash` 4+以上的版本
**示例函数:**
```sh
lower() {
# Usage: lower "string"
printf '%s\n' "${1,,}"
}
```
**示例用法:**
```shell
$ lower "HELLO"
hello
$ lower "HeLlO"
hello
$ lower "hello"
hello
```
## 将字符串转换为大写
**警告:** 需要`bash` 4+以上的版本
**示例函数:**
```sh
upper() {
# Usage: upper "string"
printf '%s\n' "${1^^}"
}
```
**示例用法:**
```shell
$ upper "hello"
HELLO
$ upper "HeLlO"
HELLO
$ upper "HELLO"
HELLO
```
## 反转字符串大小写
**警告:** 需要`bash` 4+以上的版本
**示例函数:**
```sh
reverse_case() {
# Usage: reverse_case "string"
printf '%s\n' "${1~~}"
}
```
**示例用法:**
```shell
$ reverse_case "hello"
HELLO
$ reverse_case "HeLlO"
hElLo
$ reverse_case "HELLO"
hello
```
## 删除字符串中的引号
**示例函数:**
```sh
trim_quotes() {
# Usage: trim_quotes "string"
: "${1//\'}"
printf '%s\n' "${_//\"}"
}
```
**示例用法:**
```shell
$ var="'Hello', \"World\""
$ trim_quotes "$var"
Hello, World
```
## 从字符串中删除所有正则实例
**示例函数:**
```sh
strip_all() {
# Usage: strip_all "string" "pattern"
printf '%s\n' "${1//$2}"
}
```
**示例用法:**
```shell
$ strip_all "The Quick Brown Fox" "[aeiou]"
Th Qck Brwn Fx
$ strip_all "The Quick Brown Fox" "[[:space:]]"
TheQuickBrownFox
$ strip_all "The Quick Brown Fox" "Quick "
The Brown Fox
```
## 从字符串中删除第一次出现的正则实例
**示例函数:**
```sh
strip() {
# Usage: strip "string" "pattern"
printf '%s\n' "${1/$2}"
}
```
**示例用法:**
```shell
$ strip "The Quick Brown Fox" "[aeiou]"
Th Quick Brown Fox
$ strip "The Quick Brown Fox" "[[:space:]]"
TheQuick Brown Fox
```
## 在字符串开头匹配正则并删除
**示例函数:**
```sh
lstrip() {
# Usage: lstrip "string" "pattern"
printf '%s\n' "${1##$2}"
}
```
**示例用法:**
```shell
$ lstrip "The Quick Brown Fox" "The "
Quick Brown Fox
```
## 在字符串末尾匹配正则并删除
**示例函数:**
```sh
rstrip() {
# Usage: rstrip "string" "pattern"
printf '%s\n' "${1%%$2}"
}
```
**示例用法:**
```shell
$ rstrip "The Quick Brown Fox" " Fox"
The Quick Brown
```
## 百分号编码字符串
**示例函数:**
```sh
urlencode() {
# Usage: urlencode "string"
local LC_ALL=C
for (( i = 0; i < ${#1}; i++ )); do
: "${1:i:1}"
case "$_" in
[a-zA-Z0-9.~_-])
printf '%s' "$_"
;;
*)
printf '%%%02X' "'$_"
;;
esac
done
printf '\n'
}
```
**示例用法:**
```shell
$ urlencode "https://github.com/dylanaraps/pure-bash-bible"
https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible
```
## 解码用百分比编码的字符串
**示例函数:**
```sh
urldecode() {
# Usage: urldecode "string"
: "${1//+/ }"
printf '%b\n' "${_//%/\\x}"
}
```
**示例用法:**
```shell
$ urldecode "https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible"
https://github.com/dylanaraps/pure-bash-bible
```
## 检查字符串是否包含子字符串
**用于测试:**
```shell
if [[ $var == *sub_string* ]]; then
printf '%s\n' "sub_string is in var."
fi
# 反转 (子串不在字符串中).
if [[ $var != *sub_string* ]]; then
printf '%s\n' "sub_string is not in var."
fi
# 也可以在数组中运行
if [[ ${arr[*]} == *sub_string* ]]; then
printf '%s\n' "sub_string is in array."
fi
```
**使用case语句:**
```shell
case "$var" in
*sub_string*)
# Do stuff
;;
*sub_string2*)
# Do more stuff
;;
*)
# Else
;;
esac
```
## 检查字符串是否以子字符串开头
```shell
if [[ $var == sub_string* ]]; then
printf '%s\n' "var starts with sub_string."
fi
# 反转 (变量不是以子串开头).
if [[ $var != sub_string* ]]; then
printf '%s\n' "var does not start with sub_string."
fi
```
## 检查字符串是否以子字符串结尾
```shell
if [[ $var == *sub_string ]]; then
printf '%s\n' "var ends with sub_string."
fi
# Inverse (var does not end with sub_string).
if [[ $var != *sub_string ]]; then
printf '%s\n' "var does not end with sub_string."
fi
```
<!-- CHAPTER END -->
<!-- CHAPTER START -->
# 数组
## 反转数组
启用`extdebug`允许访问`BASH_ARGV`数组,该数组反向存储当前函数的参数
**示例函数:**
```sh
reverse_array() {
# Usage: reverse_array "array"
shopt -s extdebug
f()(printf '%s\n' "${BASH_ARGV[@]}"); f "$@"
shopt -u extdebug
}
```
**示例用法:**
```shell
$ reverse_array 1 2 3 4 5
5
4
3
2
1
$ arr=(red blue green)
$ reverse_array "${arr[@]}"
green
blue
red
```
## 删除重复的数组元素
创建临时关联数组。设置关联数组值并发生重复赋值时,bash会覆盖该键。这允许我们有效地删除数组重复。
**警告:** 版本要求 `bash` 4+
**示例函数:**
```sh
remove_array_dups() {
# Usage: remove_array_dups "array"
declare -A tmp_array
for i in "$@"; do
[[ $i ]] && IFS=" " tmp_array["${i:- }"]=1
done
printf '%s\n' "${!tmp_array[@]}"
}
```
**示例用法:**
```shell
$ remove_array_dups 1 1 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5
1
2
3
4
5
$ arr=(red red green blue blue)
$ remove_array_dups "${arr[@]}"
red
green
blue
```
## 随机返回一个数组元素
**示例函数:**
```sh
random_array_element() {
# Usage: random_array_element "array"
local arr=("$@")
printf '%s\n' "${arr[RANDOM % $#]}"
}
```
bash的SHELL参数RANDOM可以生成0-32767的随机数
想设定从1到N的随机数范围的话,可以使用:
$ ( ( (RANDOM % n) + 1 ))
**示例用法:**
```shell
$ array=(red green blue yellow brown)
$ random_array_element "${array[@]}"
yellow
# Multiple arguments can also be passed.
$ random_array_element 1 2 3 4 5 6 7
3
```
## 循环迭代一个数组
每次`printf`调用时,都会打印下一个数组元素。当打印到达最后一个数组元素时,它再次从第一个元素开始。
```sh
arr=(a b c d)
cycle() {
printf '%s ' "${arr[${i:=0}]}"
((i=i>=${#arr[@]}-1?0:++i))
}
```
## 在两个值之间转换
这与上面的工作方式相同,这只是一个不同的用例。
```sh
arr=(true false)
cycle() {
printf '%s ' "${arr[${i:=0}]}"
((i=i>=${#arr[@]}-1?0:++i))
}
```
<!-- CHAPTER END -->
<!-- CHAPTER START -->
# 循环
## 循环生成范围内的数字
替代`seq`.
```shell
# Loop from 0-100 (no variable support).
for i in {0..100}; do
printf '%s\n' "$i"
done
```
## 循环遍历可变数字范围
替代 `seq`.
```shell
# Loop from 0-VAR.
VAR=50
for ((i=0;i<=VAR;i++)); do
printf '%s\n' "$i"
done
```
## 循环数组
```shell
arr=(apples oranges tomatoes)
# Just elements.
for element in "${arr[@]}"; do
printf '%s\n' "$element"
done
```
## 循环输出带索引的数组
```shell
arr=(apples oranges tomatoes)
# 元素和索引.
for i in "${!arr[@]}"; do
printf '%s %s\n' "$i ${arr[i]}"
done
# 替代方法.
for ((i=0;i<${#arr[@]};i++)); do
printf '%s %s\n' "$i ${arr[i]}"
done
```
## 循环遍历文件的内容
```shell
while read -r line; do
printf '%s\n' "$line"
done < "file"
```
## 循环遍历文件和目录
不要 `ls`.
```shell
# 遍历当前目录下的文件和目录.
for file in *; do
printf '%s\n' "$file"
done
# 遍历目录中的png图片.
for file in ~/Pictures/*.png; do
printf '%s\n' "$file"
done
# 迭代输出目录.
for dir in ~/Downloads/*/; do
printf '%s\n' "$dir"
done
# 支持扩展.
for file in /path/to/parentdir/{file1,file2,subdir/file3}; do
printf '%s\n' "$file"
done
# 递归迭代,输出子目录下的所有文件.
#
shopt -s globstar
for file in ~/Pictures/**/*; do
printf '%s\n' "$file"
done
shopt -u globstar
```
globstar是Bash 4.0才引入的选项,当设置启用globstar(shopt -s globstar)时,两个星号意为对通配符进行展开就可以匹配任何当前目录(包括子目录)以及其的文件;若不启用globstar(shopt -u globstar),两个星号通配符的作用和一个星号通配符是相同的。
<!-- CHAPTER END -->
<!-- CHAPTER START -->
# 文本处理
**警告:** `bash`在小于`<4.4`的版本中不能正确处理二进制数据.
## 读取文件到一个字符串中
替代 `cat` 命令.
```shell
file_data="$(<"file")"
```
## 读取文件到一个数组中 (*按行读取*)
替代 `cat` 命令.
```shell
# Bash <4 (丢弃空行)
IFS=$'\n' read -d "" -ra file_data < "file"
# Bash <4 (保留空行).
while read -r line; do
file_data+=("$line")
done < "file"
# Bash 4+
mapfile -t file_data < "file"
```
## 获取文件的前N行
替代 `head` 命令.
**警告:** 版本要求 `bash` 4+
**示例函数:**
```sh
head() {
# Usage: head "n" "file"
mapfile -tn "$1" line < "$2"
printf '%s\n' "${line[@]}"
}
```
**示例用法:**
```shell
$ head 2 ~/.bashrc
# Prompt
PS1='➜ '
$ head 1 ~/.bashrc
# Prompt
```
## 获取文件的最后N行
替代 `tail` 命令.
**警告:** 版本要求 `bash` 4+
**示例函数:**
```sh
tail() {
# Usage: tail "n" "file"
mapfile -tn 0 line < "$2"
printf '%s\n' "${line[@]: -$1}"
}
```
**示例用法:**
```shell
$ tail 2 ~/.bashrc
# Enable tmux.
# [[ -z "$TMUX" ]] && exec tmux
$ tail 1 ~/.bashrc
# [[ -z "$TMUX" ]] && exec tmux
```
## 获取文件中的行数
替代 `wc -l`.
**示例函数 (bash 4):**
```sh
lines() {
# Usage: lines "file"
mapfile -tn 0 lines < "$1"
printf '%s\n' "${#lines[@]}"
}
```
**示例函数 (bash 3):**
这个方法比`mapfile`方法使用更少的内存,并且在`bash` 3中工作,但对于更大的文件来说它更慢。
```sh
lines_loop() {
# Usage: lines_loop "file"
count=0
while IFS= read -r _; do
((count++))
done < "$1"
printf '%s\n' "$count"
}
```
**示例用法:**
```shell
$ lines ~/.bashrc
48
$ lines_loop ~/.bashrc
48
```
## 计算目录中的文件或目录
这是通过将glob的输出传递给函数然后计算参数的数量来实现的。
**示例函数:**
```sh
count() {
# Usage: count /path/to/dir/*
# count /path/to/dir/*/
printf '%s\n' "$#"
}
```
**示例用法:**
```shell
# Count all files in dir.
$ count ~/Downloads/*
232