命令替换

命令替换可以把一个命令的输出作为另一个命令的参数,或者赋值给变量。这是 Shell 脚本中动态获取信息的主要方式,非常常用。

两种语法

$() 语法

推荐使用 $() 语法:

result=$(ls)
echo $(date)
files=$(find . -name "*.txt")

反引号语法

旧语法,不推荐:

result=`ls`
echo `date`

$() 的优势:

  • 可以嵌套
  • 更易读
  • 不容易与单引号混淆

基本用法

赋值给变量

now=$(date)
hostname=$(hostname)
user_count=$(who | wc -l)

作为命令参数

echo "当前时间:$(date)"
echo "当前目录:$(pwd)"
tar -czf backup_$(date +%Y%m%d).tar.gz /data

在循环中使用

for file in $(ls *.txt); do
    echo "处理 $file"
done

嵌套命令替换

$() 可以嵌套:

echo "内核版本:$(uname -r)"
echo "用户目录:$(echo $(whoami) 的家目录)"

更实用的例子:

current_ip=$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | head -1 | awk '{print $2}' | cut -d/ -f1)

反引号不能直接嵌套,需要转义:

result=`echo \`date\``

保留换行符

命令替换会删除末尾的换行符:

text=$(echo -e "第一行\n第二行\n第三行")
echo "$text"

输出:

第一行
第二行
第三行

用引号包裹变量可以保留内部换行符。但末尾的换行符会被删除:

text=$(echo -e "第一行\n第二行\n\n\n")
echo "[$text]"

输出:

[第一行
第二行]

与管道配合

biggest=$(du -sh * | sort -h | tail -1)
error_count=$(grep -c "ERROR" app.log)
unique_ips=$(awk '{print $1}' access.log | sort -u)

常见场景

获取系统信息

os=$(uname -s)
kernel=$(uname -r)
cpu=$(nproc)
memory=$(free -h | awk '/^Mem:/{print $2}')

生成文件名

backup_file="backup_$(date +%Y%m%d_%H%M%S).tar.gz"
log_file="app_$(date +%Y%m%d).log"

检查命令是否存在

if command -v git &> /dev/null; then
    echo "Git 已安装"
fi

获取脚本所在目录

script_dir=$(cd "$(dirname "$0")" && pwd)

获取进程信息

pid=$(pgrep nginx)
port=$(netstat -tlnp | grep nginx | awk '{print $4}' | cut -d: -f2)

性能考虑

命令替换会创建子 Shell,频繁调用会影响性能:

for i in {1..100}; do
    result=$(some_command)
done

如果可能,把命令移到循环外:

result=$(some_command)
for i in {1..100}; do
    echo "$result"
done

命令替换 vs 进程替换

命令替换捕获输出作为字符串:

result=$(echo "hello")

进程替换把输出当作文件:

diff <(ls dir1) <(ls dir2)

小结

  • $(command) 是推荐的命令替换语法
  • 命令替换可以嵌套
  • 用引号包裹变量保留换行符
  • 常用于获取系统信息、生成文件名
  • 会创建子 Shell,注意性能影响
  • 与进程替换不同,命令替换返回字符串