ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Shell变量与参数详解:环境变量、位置参数、特殊变量

Shell变量与参数详解:环境变量、位置参数、特殊变量 Shell变量与参数详解环境变量、位置参数、特殊变量上一篇把变量怎么定义讲了个大概。真到写脚本时你会立刻遇到三个问题变量怎么传给子进程、脚本怎么接收调用者传进来的参数、以及那些$?、$#、$到底代表什么。这一篇把环境变量、位置参数和特殊变量系统过一遍它们是脚本和外部世界交换数据的全部接口。一、变量的作用域局部变量与环境变量先看一段代码感受一下区别# parent.sh nameTom export age18 bash child.sh# child.sh echo name$name echo age$age运行bash parent.sh预期输出name age18name在子进程里是空的age却被带过去了。差别就在export。不加export的变量只在当前 shell 可见子进程包括脚本、其他命令看不到。加了export的变量会被导出到环境里所有子进程都能继承。已经定义的变量事后加export age也能让它变成环境变量不用重新赋值。常用环境变量变量含义典型值PATH命令搜索路径冒号分隔/usr/local/bin:/usr/bin:/binHOME当前用户家目录/home/userPWD当前工作目录随时变化USER/LOGNAME当前用户名userSHELL当前登录 Shell/bin/bashLANG系统语言环境en_US.UTF-8TERM终端类型xterm-256colorHOSTNAME主机名服务器名查看全部环境变量用env或printenvprintenv PATH预期输出/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin⚠️ 常见错误改了PATH不生效在脚本里写PATH$PATH:/opt/app/bin跑完就没了因为脚本是子进程。要永久生效得写进~/.bashrc或/etc/profile.d/里然后source。PATH拼错冒号PATH$PATH/opt/bin少了冒号会把原来的整个路径替换成一长串所有命令都报command not found。改PATH之前先echo $PATH备份一下。环境变量被子进程改了不影响父进程子进程继承的是一份拷贝在子进程里改export age20父 shell 的age不会变。想影响父进程只能source。二、位置参数脚本怎么收参数运行脚本时在命令名后面跟的参数在脚本里按位置取出来就是位置参数。#!/bin/bash echo 脚本名是: $0 echo 第一个参数: $1 echo 第二个参数: $2 echo 第三个参数: $3保存为greet.sh执行chmod x greet.sh ./greet.sh Alice Beijing 25预期输出脚本名是: ./greet.sh 第一个参数: Alice 第二个参数: Beijing 第三个参数: 25位置参数的编号规则写法含义$0脚本本身的名字含你调用它时写的路径$1~$9第 1 到第 9 个参数${10}、${11}第 10 个及以后必须加花括号$#一共传进来几个参数$*所有参数合成一个字符串$所有参数每个参数独立为什么${10}要加花括号因为$10会被 shell 解析成$1 后面跟个 0输出第一个参数的值再拼个字符 0。超过 9 个参数时不加花括号一定翻车。shift把参数队列往前挪位置参数是个队列shift命令能把整个队列左移一位原来的$2变成新的$1#!/bin/bash echo 一开始有 $# 个参数 while [ $# -gt 0 ]; do echo 处理参数: $1 shift done执行./shift.sh a b c预期输出一开始有 3 个参数 处理参数: a 处理参数: b 处理参数: c这种写法在解析可选参数比如-f、-o file时非常好用。三、特殊变量一览Shell 预定义了一批特殊变量全部大写用来拿运行时的状态。变量含义$?上一条命令的退出码0 表示成功非 0 表示失败$$当前脚本的进程号PID$!最近一个后台进程的 PID$#位置参数个数$*所有位置参数合为一个串$所有位置参数独立逐项$-当前 shell 的选项标志$_上一条命令的最后一个参数$?脚本里最常打交道的变量ls /etc/hostname echo ls 成功退出码: $? ls /notexist echo ls 失败退出码: $?预期输出/etc/hostname ls 成功退出码: 0 ls: cannot access /notexist: No such file or directory ls 失败退出码: 2脚本里判断上一条命令成不成功靠的就是$?。下一篇讲if条件判断时会大量用到它。自己写脚本exit 1就能主动返回错误码调用方据此判断要不要继续。$$和$!进程号$$在脚本里常用于生成临时文件的唯一名避免多开冲突tmpfile/tmp/myapp_$$.tmp echo 临时文件: $tmpfile touch $tmpfile预期输出PID 每次运行都不同临时文件: /tmp/myapp_12345.tmp$!配合后台任务用。启动sleep 30 后立刻echo $!就能拿到那个后台 sleep 的 PID后面好 kill 它。四、$*和$的区别面试和实战都爱考这两个不加引号时几乎一样一加引号就彻底分家。#!/bin/bash echo --- 用 $* --- for arg in $*; do echo 一个元素: [$arg] done echo --- 用 $ --- for arg in $; do echo 一个元素: [$arg] done执行./diff.sh apple red fruit banana预期输出--- 用 $* --- 一个元素: [apple red fruit banana] --- 用 $ --- 一个元素: [apple] 一个元素: [red fruit] 一个元素: [banana]带引号时$*把所有参数拼成一个字符串中间用 IFS默认空格连接$保持每个参数独立带空格的参数不会被拆碎。写循环处理参数时永远用$这是唯一安全的写法。完整示例把所有参数原样传给另一个命令#!/bin/bash # my_grep.sh: 包装一下 grep加颜色 grep --colorauto $这样./my_grep.sh error /var/log/syslog会把error和/var/log/syslog两个参数原样转给 grep。如果改成grep --colorauto $*带空格的搜索词就会被拆成好几个参数grep 直接报错。五、read让脚本停下来等你输入参数不够用的时候脚本还能在运行中交互提问#!/bin/bash read -p 你叫什么名字? username read -p 你今年多大? age echo 你好 $username再过一年你就 $((age 1)) 岁了执行./ask.sh预期输出斜体部分是用户输入你叫什么名字? Benothing 你今年多大? 22 你好 Benothing再过一年你就 23 岁了read的几个常用选项选项作用-p 提示语输入前打印一行提示-s静默输入打字不回显适合密码-t 秒数超时自动放弃-a 数组名读入一组词存进数组写密码提示时一定要配-s否则密码会清清楚楚打在屏幕上。六、知识扩展环境变量是怎么被子进程继承的你在终端敲export age18然后bash child.shchild.sh里的age从哪来这不是 shell 的魔法是操作系统提供的环境块。每个进程在内核里都有两块内存一块是命令行参数argv另一块叫环境块envp。父进程fork()出子进程时内核会把父进程的环境块完整拷贝一份给子进程。子进程里改任何环境变量改的都是自己那份拷贝父进程看不到。export干的事是把一个 shell 变量从shell 私有变量标记成需要放进环境块。下次 fork 子进程时这个变量就跟着拷贝过去了。这条机制解释了三个常见现象为什么/etc/profile、~/.bashrc改完要source因为它们是在当前 shell 里执行的改完立刻对当前进程生效如果直接bash profile.sh执行变量跑完就随子进程一起消失了。为什么 Docker 容器里-e KEYval能传进容器因为容器本质是宿主机上的一个子进程启动时把 KEYval 塞进了它的环境块。为什么 C 语言里main(int argc, char *argv[], char *envp[])能拿到环境变量就是内核传给它的这块内存。脚本对外传数据用export 子进程脚本对内收数据用位置参数和$。把这两组接口用熟自动化脚本就能和外部系统接得很干净。
返回列表