为什么学习Bash one-liners?因为它们简洁、高效,且充分利用了Bash的内置特性(如参数扩展、重定向和历史扩展),减少了对外部工具的依赖。通过掌握Bash的单行命令(one-liners),你可以显著提高工作效率,避免编写冗长的脚本,同时培养出一种"壳思维"------用最少的代码实现最多的功能。
学习参考:
Bash One-Liners Explained, Part I: Working with files https://catonmat.net/bash-one-liners-explained-part-one
Bash One-Liners Explained, Part II: Working with strings https://catonmat.net/bash-one-liners-explained-part-two
Bash One-Liners Explained, Part III: All about redirections https://catonmat.net/bash-one-liners-explained-part-three
Bash One-Liners Explained, Part IV: Working with history https://catonmat.net/bash-one-liners-explained-part-four
Bash One-Liners Explained, Part V: Navigating around https://catonmat.net/bash-one-liners-explained-part-five
Bash命令行历史的权威指南 附录https://catonmat.net/the-definitive-guide-to-bash-command-line-history
Bash Reference Manual https://tiswww.case.edu/php/chet/bash/bashref.html
Part I: 文件操作
文件操作是Bash脚本的基础。Bash提供内置的读写机制、重定向和参数扩展,让你无需外部工具如cat或sed就能完成许多任务。以下one-liners聚焦于高效的文件处理,参考Catonmat系列Part I。
1. 清空文件(截断为0字节)
bash
> file.txt
这个命令使用输出重定向>打开file.txt进行写入。如果文件存在,它会被清空;如果不存在,则创建空文件。由于没有实际内容被重定向,文件保持为空。根据Bash手册,重定向>等价于1> file,其中1是stdout的文件描述符(FD 1)。这是一个原子操作,避免了竞争条件。
要写入内容:
bash
echo "Hello, Bash!" > file.txt
2. 向文件追加字符串
bash
echo "追加的内容" >> file.txt
>>是追加模式,等价于1>> file。如果文件不存在,会创建它。echo默认添加换行符。要避免换行,使用-n选项:
bash
echo -n "无换行追加" >> file.txt
Bash手册强调,追加模式不会截断现有内容,确保数据完整性。
3. 从文件读取第一行到变量
bash
read -r line < file.txt
read从stdin读取一行,-r防止反斜杠转义。< file.txt将文件重定向到stdin。默认IFS(内部字段分隔符,默认为空格、制表符和换行)会修剪前后空白。要保留空白:
bash
IFS= read -r line < file.txt
备选方案使用命令替换:
bash
line=$(head -1 file.txt)
$(...)捕获head输出,比反引号...更现代,支持嵌套。
4. 逐行读取文件
bash
while read -r line; do
echo "处理: $line"
done < file.txt
这是一个while循环,read每次读取一行,直到EOF。输入重定向< file.txt确保高效。保留空白:
bash
while IFS= read -r line; do
echo "处理: $line"
done < file.txt
管道版本(较慢,因子壳):
bash
cat file.txt | while read -r line; do
echo "处理: $line"
done
Bash手册指出,while循环中的重定向是高效的,因为它在父壳中执行。
5. 读取文件中的随机一行
bash
read -r random_line < <(shuf file.txt)
过程替换<(...)将shuf(GNU核心utils的随机打乱工具)输出作为read的输入。shuf随机排列行,第一行被读取。
备选使用sort:
bash
read -r random_line < <(sort -R file.txt)
或命令替换:
bash
random_line=$(shuf -n 1 file.txt)
这些方法利用Bash 4+的过程替换,创建匿名管道。
6. 读取文件的前三个字段到变量
bash
while read -r field1 field2 field3 _; do
echo "字段1: $field1, 2: $field2, 3: $field3"
done < file.txt
read基于IFS拆分行,前三个字段赋值给变量,剩余到_(占位符)。如果行正好三个字段,省略_。
示例:解析wc输出:
bash
read lines words chars _ < <(wc file.txt)
echo "行数: $lines, 单词: $words, 字符: $chars"
这里字符串<<<用于字符串拆分:
bash
read packets _ _ time _ <<< "20 packets in 10 seconds"
echo "数据包: $packets, 时间: $time"
Bash手册解释,read -a可将字段存入数组:IFS=' ' read -ra fields <<< "$str"。
7. 获取文件大小到变量
bash
size=$(wc -c < file.txt)
wc -c计数字节,< file.txt重定向输入,避免输出文件名。结果如1024,不含文件名。
8. 从路径提取文件名
bash
path="/dir/sub/file.txt"
filename=${path##*/}
echo "$filename" # 输出: file.txt
参数扩展${var##pattern}移除开头最长匹配的*/(最后一个斜杠)。
9. 从路径提取目录名
bash
dirname=${path%/*}
echo "$dirname" # 输出: /dir/sub
${var%pattern}移除末尾最短匹配的/*。
10. 快速复制文件
bash
cp /path/to/file{,_copy}
大括号扩展{a,b}生成file file_copy。类似移动:
bash
mv file{,_backup}
这些one-liners展示了Bash的简洁性:重定向、参数扩展和扩展让文件操作如行云流水。实践这些,你会发现外部工具如cp在简单场景下可被内置替换。
Part II: 字符串处理
字符串是Bash的核心数据类型。Part II聚焦于生成、连接、拆分和替换,使用printf、read和参数扩展。Bash手册的"参数扩展"部分是这些技巧的基础。
1. 生成a-z字母表
bash
echo {a..z}
大括号序列{a..z}生成a到z,按字典序展开。输出:a b c ... z。
2. 无空格的a-z字母表
bash
printf "%c" {a..z}
printf "%c"打印字符,无换行。输出:abcdefghijklmnopqrstuvwxyz。添加换行:
bash
printf "%c" {a..z} $'\n'
$'\n'是ANSI-C引述的换行。每个字母新行:
bash
printf "%c\n" {a..z}
存入变量(Bash 4+):
bash
printf -v alphabet "%c" {a..z}
echo "$alphabet"
数字序列:
bash
echo {1..10}
# 或 seq 1 10
3. 填充0-9的前导零
bash
printf "%02d " {0..9}
%02d零填充到2位整数。输出:00 01 ... 09。Bash 4+直接:
bash
echo {00..09}
4. 生成30个英文单词
bash
echo {w,t,}h{e{n{,ce{,forth}},re{,in,fore,with{,al}}},ither,at}
嵌套大括号生成排列,如when、whence等。展示了扩展的组合威力。
5. 复制字符串10次
bash
echo foo{,,,,,,,,,,}
10个空字符串与foo组合,生成10个foo。
6. 连接两个字符串
bash
x="Hello"; y=", World!"
echo "$x$y" # 输出: Hello, World!
引号防止词拆分。无引号危险:
bash
x="-n"; y=" foo"
echo $x$y # 输出: foo(-n被echo视为选项)
赋值:var=$x$y(无需引号)。
7. 用给定字符拆分字符串
bash
str="foo-bar-baz"
IFS=- read -r x y z <<< "$str"
echo "$x $y $z" # foo bar baz
IFS=-设置分隔符,<<<是这里字符串。存入数组:
bash
IFS=- read -ra parts <<< "$str"
echo "${parts[0]}" # foo
8. 逐字符处理字符串
bash
str="hello"
while IFS= read -rn1 c; do
echo "$c"
done <<< "$str"
-rn1读取一个字符,-r原始模式。
9. 在字符串中替换"foo"为"bar"
bash
str="foo foo bar"
echo ${str/foo/bar} # 替换第一个: foo bar bar
echo ${str//foo/bar} # 替换所有: bar bar bar
参数扩展${var/pattern/replacement}。Bash手册详述://全局替换。
10. 检查字符串匹配模式
bash
file="archive.zip"
if [[ $file = *.zip ]]; then
echo "是ZIP文件"
fi
[[ ]]支持glob:*任意字符串,?单字符,[a-z]范围。忽略大小写:
bash
shopt -s nocasematch
if [[ $answer = [Yy]* ]]; then
echo "是"
fi
11. 检查字符串匹配正则表达式
bash
str="1.23"
if [[ $str =~ [0-9]+\.[0-9]+ ]]; then
echo "是数字"
fi
=~是正则运算符,匹配man 3 regex语法。捕获组在BASH_REMATCH数组。
12. 获取字符串长度
bash
str="hello"
echo ${#str} # 5
${#var}返回长度。
13. 从字符串提取子串
bash
str="hello world"
echo ${str:6} # world(从索引6开始,0基)
echo ${str:7:2} # or(从7取2字符)
负偏移从末尾:${str: -5}为world。
14. 大写字符串
Bash 4+:
bash
str="foo bar"
echo ${str^^} # FOO BAR
或declare -u var; var="$str"; echo "$var"。
15. 小写字符串
bash
echo ${str,,} # foo bar
或declare -l var。
这些字符串技巧利用Bash的内置扩展,避免了sed或awk。在脚本中,参数扩展比外部工具更快。
Part III: 重定向
重定向是Bash的I/O心脏,管理文件描述符(FD:0=stdin, 1=stdout, 2=stderr)。Part III详解从基本到高级的重定向,Bash手册的"重定向"章节是权威来源。
1. 重定向stdout到文件
bash
ls > list.txt
>截断文件,等价于1> list.txt。FD 1指向文件。
2. 重定向stderr到文件
bash
ls nonexist 2> errors.log
2> errors.log捕获错误。
3. 重定向stdout和stderr到文件
bash
ls nonexist &> output.log
&>合并,等价于> output.log 2>&1。顺序重要:2>&1 > file只重定向stdout。
4. 丢弃输出
bash
ls nonexist > /dev/null 2>&1
/dev/null吞噬数据。或&> /dev/null。
5. 从文件重定向到stdin
bash
read line < input.txt
FD 0指向文件。
6. 通过here-document重定向多行文本
bash
cat <<EOF
行1
行2
EOF
直到EOF结束。缩进用<<- EOF(允许前导Tab)。
7. 通过here-string重定向单行
bash
read line <<< "单行输入"
等价于echo "单行" | read line,但更高效。
8. 使用exec全局重定向stderr
bash
exec 2> errors.log
ls nonexist # 错误到log
exec 2>&1 # 恢复
exec持久化重定向。
9. 用自定义FD打开文件读取
bash
exec 3< input.txt
read line <&3
exec 3<&- # 关闭
FD 3从文件读取。
10. 用自定义FD打开文件写入
bash
exec 4> output.txt
echo "数据" >&4
exec 4>&-
11. 双向打开文件
bash
exec 5<> file.txt
echo "追加" >&5
read line <&5
exec 5>&-
<>允许读写。
12. 多命令输出到文件
bash
(ls; pwd) > combined.txt
子壳( )分组。
13. 通过命名管道(FIFO)执行命令
bash
mkfifo mypipe
exec < mypipe # Shell 1
exec 3> mypipe; echo "ls" >&3 # Shell 2
FIFO在文件系统中创建管道。
14. 通过/dev/tcp访问网站
bash
exec 3<>/dev/tcp/www.example.com/80
echo -e "GET / HTTP/1.1\r\n\r\n" >&3
cat <&3
exec 3>&-
Bash扩展,支持TCP/UDP。
15. 防止覆盖文件(noclobber)
bash
set -o noclobber
ls > existing.txt # 错误
ls >| existing.txt # 强制
16. 复制输出到文件和stdout
bash
ls | tee list.txt
tee分流。
17. 管道stdout
bash
ls | grep txt
标准管道。
18. 管道stdout和stderr
bash
ls nonexist |& grep error # Bash 4+
# 或 ls nonexist 2>&1 | grep error
19. 命名文件描述符(Bash 4.1+)
bash
exec {log}> errors.log
echo "错误" >&$log
exec {log}>&-
{var}分配FD。
20. 重定向顺序灵活
bash
echo hello > file.txt
# 等价于 echo > file.txt hello
21. 交换stdout和stderr
bash
ls nonexist 3>&1 1>&2 2>&3 3>&-
临时FD 3交换1和2。
22. stdout和stderr到不同进程
bash
ls > >(grep txt) 2> >(grep error >&2)
过程替换>(cmd)创建FIFO。
23. 获取管道所有命令的退出码
bash
cmd1 | cmd2 | cmd3
echo ${PIPESTATUS[@]} # [0, 0, 1] 等
PIPESTATUS数组记录每个管道的退出码。
重定向的威力在于FD管理:Bash从0-255支持FD,关闭未用FD避免泄漏。
Part IV: 历史记录
历史记录让Bash"记住"过去命令。Part IV介绍基本操作,Bash手册的"历史"部分详述变量如HISTSIZE。
1. 擦除所有历史
bash
rm ~/.bash_history
删除~/.bash_history。注销后,rm本身会被记录。
2. 停止本会话记录历史
bash
unset HISTFILE
# 或 HISTFILE=/dev/null
HISTFILE控制文件。
3. 不记录当前命令
bash
command # 以空格开头
设置HISTIGNORE="[ ]*"忽略空格开头。作者设置:HISTIGNORE="&:[ ]*"(忽略重复和空格)。
4. 更改历史文件
bash
HISTFILE=~/my_history
后续命令保存到新文件。
5. 添加时间戳
bash
HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S "
history
strftime格式显示时间。
6. 显示历史
bash
history
带编号显示所有命令。
7. 显示最近50条
bash
history 50
8. 显示前10个最常用命令
bash
history | awk '{print $2}' | sort | uniq -c | sort -rn | head -10
管道分析频率(简化版)。
9. 执行上一个命令
bash
!!
快速重跑,如sudo !!。
10. 执行最近以字符串开头的命令
bash
!ls
匹配最近ls命令。
11. 在编辑器中打开上一个命令
bash
fc
fc(fix command)打开默认编辑器编辑并执行。
历史扩展!是Bash的杀手锏,详见下一扩展部分。
Part V: 导航技巧
Bash使用readline库支持Emacs-style编辑。Part V列出快捷键,提高命令行效率。
基本移动
Ctrl+a:行首Ctrl+e:行尾Ctrl+b/Ctrl+f:左/右一字符Esc+b/Esc+f:左/右一词
删除与粘贴
Ctrl+w:删除前一词(杀掉,存kill ring)Ctrl+y:粘贴杀掉的词Ctrl+u:删除整行Ctrl+h/Ctrl+d:删左/右字符Ctrl+t:交换相邻字符Esc+t:交换相邻词
历史搜索
Ctrl+r:反向搜索历史(输入部分命令)Ctrl+s:正向搜索(需stty stop undef防冻结)
转换
Esc+u:词尾大写Esc+l:词尾小写Esc+c:词首大写
其他
Ctrl+v:插入原始字符(如Tab)Esc+#:注释当前行Ctrl+x Ctrl+e:在编辑器打开命令Esc+.:插入上命令最后一个参数Ctrl+l:清屏Ctrl+x Ctrl+u:增量撤销
切换Vi模式:set -o vi。
这些快捷键让命令行如IDE般流畅。
扩展: Bash命令行历史的权威指南
基于Catonmat的指南,深度探索历史。
键盘快捷键
Emacs:Ctrl+p/n上/下历史;Ctrl+r反搜。
Vi:Esc命令模式,k/j上/下;/搜索。
事件设计符
!!:上命令!str:最近以str开头!?str?:包含str!n:第n条^old^new^:替换old为new
词设计符:!!:1第一参数;!!:$最后一个;!!:*所有参数。
修饰符::p打印不执行;:r移除后缀;:h头目录;:s/old/new/替换。
配置
变量:
HISTSIZE=1000:内存大小HISTFILESIZE=2000:文件大小HISTIGNORE="ls:cd:exit":忽略模式HISTCONTROL=ignoreboth:忽略重复和空格
shopt:
shopt -s histappend:追加历史shopt -s histverify:验证扩展
自定义提示:PS1='\u@\h:\w\!\$ '显示历史号。
管理
history -c清空;history -d n删第n条。
这个指南让历史成为生产力工具。
参考与最佳实践
从Bash手册:
- 扩展:优先参数扩展而非外部命令。
- 引号:始终双引号变量防拆分。
- IFS :小心设置,默认为
<space><tab>\n。 - shopt :
shopt -s extglob启用扩展glob。 - 安全 :用
[[ ]]测试,避免[的转义地狱。
最佳实践:
- 用
printf代替echo(更可移植)。 - 管道用
PIPESTATUS检查错误。 - 历史用
&忽略重复。 - 测试:
bash -n script.sh语法检查。