1. 问题出现:
我为了实现一个功能, 就是让 PS1 变量 (命令行提示符) 每隔 1 分钟 (利用 crontab 计划任务) 变化一次颜色和背景格式以实现酷炫的效果, 但是经过了各种尝试均以失败告终. 虽然能够实现让 PS1 每按一次回车变化一次颜色(这个有人想尝试的话下面写的有), 但是无法做到让它每隔一段时间进行一次格式的变化
为了解决这个问题, 进行了一些研究, 总结了一下写在下面
附加: PS1 每按一次回车实现颜色变化实现:
先在脚本中写入:
- #!/bin/bash
- PS1="\033[01;\$[RANDOM%7 31]m\A[\u@\h \w]\\$\033[0m"
然后命令行直接 source 此脚本即可(或者把它放到 / etc/profile.d 文件夹下, 每次开机开启 shell 后就会自动执行)
注意点:
这里当然也可以直接在命令行中输入脚本中所写的那个命令, 不过这样的话关闭 shell 后下次就会丢失
PS1 的值不能用 RANDOM 计算过之后的值, 不然它只能在 source 的时候执行一次并取了一次 RANDOM 的值, 之后就固定了, 而这里所写的 PS1 利用了 \$[RANDOM]把它给注释掉了, 因此此时查看 PS1 可知:
- 11:01[root@centos7 /data/scriptest]# echo $PS1
- \033[01;$[RANDOM%7+31]m\A[\u@\h \w]\$\033[0m
因此 PS1 每按一次回车就会重新执行上面所写的变量赋值(显示出 PS1 命令提示符), 所以可以实现每次按回车变换颜色的功能.
由此也可以得出在 Linux 中命令提示符的值是每按一次回车就会根据 PS1 变量中所写的内容进行输出并显示在屏幕上的, 而并非读入内存之后就一成不变了
2. 以下是问题的分析和总结:
环境变量
我们知道, 环境变量 (全局变量) 虽然能够在当前 shell 以及它的子 shell 中使用, 但在子 shell 中仅仅是调用它而已, 虽然能继承并使用这个变量的值, 但是这个子 shell 并不能改变它所调用的环境变量的值并传递给父 shell 中. 注意这和函数不同, 看下面的例子:
- 20:59[root@centos7 /data/scriptest]# declare -x aaa=12345
- 20:59[root@centos7 /data/scriptest]# echo $aaa
- 12345
- 20:59[root@centos7 /data/scriptest]# ./testsource2.sh
- 12345
- 123123
- 20:59[root@centos7 /data/scriptest]# echo $aaa
- 12345
而定义一个函数则它便是在当前 shell 中运行的, 并未开启子 shell, 因此若不用 local 命令定义局部变量, 则环境变量会被函数给改变.
这里在命令行中定义函数的时候注意中括号中每个命令后面都要加上分号, 且前面的中括号要和命令之间有空格, 后面的没要求.
没定义 local
- 21:01[root@centos7 /data/scriptest]# echo $aaa
- 12345
- 21:01[root@centos7 /data/scriptest]# funsor() {
- aaa=555 ; return 0 ;
- }
- 21:02[root@centos7 /data/scriptest]# echo $aaa
- 12345
- 21:02[root@centos7 /data/scriptest]# funsor
- 21:02[root@centos7 /data/scriptest]# echo $aaa
- 555
定义 local
- 21:08[root@centos7 /data/scriptest]# echo $aaa
- 12345
- 21:08[root@centos7 /data/scriptest]# funsor2() {
- local aaa=555 ; echo $aaa ; return 0 ;
- }
- 21:08[root@centos7 /data/scriptest]# funsor2
- 555
- 21:08[root@centos7 /data/scriptest]# echo $aaa
- 12345
特别注意点(目前测试过的, 下面有测试过程 A 和 B):
自己在 shell 开启后的命令行中或者说在开启 shell 的时候载入的配置文件中定义的环境变量, 只要不是下面中所说的极特殊的那些(不能被直接继承的), 都能够在当前 shell 中开启的子 shell 中被继承.
系统 (shell 自动配置的) 中常用的环境变量, 在 Linux 的 bash shell 中, 如果再次开启子 bash shell, 按照分析得知应该能全部被继承(因为环境变量的特性), 但测试得知并非完全如此:
而能够被这个子 shell 直接继承的有(基本上在开机后 shell 开启后用 declare -x 命令查看到的这些出现的变量都能够继承):
PATH 变量
- PATH
- PWD
- HOSTNAME
- HISTSIZE
- HISTCONTROL
- PS3:
它是命令 select 后选择时出现的选择提示符, 默认是没有定义的空字符, 且默认不是环境变量, 此时会显示 #?.
经过测试把它定义为环境变量并赋值之后, 在子 shell 中能够直接继承父 shell 的 PS3
不能够被直接继承的有:
PS1:
它就是命令行的提示符的值, 可以有很多格式, 具体查看帮助 man bash.
它默认不是一个环境变量, 但是就算用命令 declare -x 把它定义为了环境变量, 子 shell 也无法继承, 子 shell 中经过测试为空;
由此可见这个 PS1 应该是一个特殊变量, 这也侧面解释了 bash shell 开启的时候默认没把它定义为环境变量, 下面几个 PS 同理
PS2:
同上, 默认不是环境变量, 定义为环境变量之后也无法继承;
它是一个非常长的命令可以通过在末尾加 "\" 使其分行显示后的多行命令的默认提示符, 默认已经定义为普通变量, 值为 ">"
PS4:
同上
它就是 set -x 命令用来修改跟踪输出的前缀, 默认定义为普通变量且值为 "+"
更多的其他还没测试, 以后补充, 不过从 1 中可见有些特殊变量就算定义为了全局变量, 在子 shell 开启的时候也会把它覆盖掉从而无法继承(相当于在 shell 开启过程是中重新声明定义了这些变量, 这个就是开启 shell 时的内部逻辑了)
注意, 以上说的将 PS 定义为环境变量都是开启 shell 时之后的操作, 只是存入内存中了, 如果另开新的 shell, 这些操作都会失效, 恢复到默认的 shell 设置
同时我们可以想到, 只要能找到定义 PS1,PS2,PS3,PS4 的配置文件位置(shell 开启时), 并将它修改为自己想要的值,(就比如上面的 PS1 每次都改变的命令), 这样每次开启一个新的 shell, 就算这些环境变量不能继承, 但是按照 shell 开启时载入的配置文件中写的这些特殊的环境变量默认设置, 就能部分实现自己想要的设置. 不过更方便的方法还是直接在子 shell 中 source 一个配置文件即可, 这个配置文件中写上这些环境变量的赋值即可, 唯一的缺憾就是不能修改后传递给父 shell(这部分不理解先把下面的分析看完再回头看).
测试过程 A:PS 的继承变量的测试(可先把下面的分析看完再回头看):
首先新开一个终端 (也就是新开 shell) 测试
文件 (脚本) 中所写:
- PStest
- #!/bin/bash
- echo PS1=$PS1
- echo PS2=$PS2
- echo PS3=$PS3
- echo PS4=$PS4
- select i in test1 test2 test3; do
- case $i in
- *)
- echo $i
- break
- ;;
- esac
- done
在当前 shell 中 source 这个文件结果:
可以看到默认的 PS 变量和 select 提示符:
- 12:10[root@centos7 /data/scriptest]# . PStest
- PS1=\[\033[01;35m\]\A[\u@\h \w]\$\[\033[00m\]
- PS2=>
- PS3=
- PS4=+
- 1) test1
- 2) test2
- 3) test3
- #? 2
- test2
- 12:10[root@centos7 /data/scriptest]#
在当前 shell 中把 PS 变量都定义为环境变量:
- 12:15[root@centos7 /data/scriptest]# declare -x PS1 PS2 PS3 PS4
- 12:15[root@centos7 /data/scriptest]# declare -x : 查看
- declare -x PS1="\\[\\033[01;35m\\]\\A[\\u@\\h \\w]\\\$\\[\\033[00m\\]"
- declare -x PS2=">"
- declare -x PS3
- declare -x PS4="+"
先在子 shell 中直接测试, 也就是直接执行此文件以脚本方式:
可见就算定义为环境变量, PS1 和 PS2 也没有继承, 子 shell 中为空. 目前还无法判断 PS3 和 PS4
- 12:15[root@centos7 /data/scriptest]# PStest
- PS1=
- PS2=
- PS3=
- PS4=+
- 1) test1
- 2) test2
- 3) test3
- #? 1
- test1
- 12:17[root@centos7 /data/scriptest]#
然后在当前 shell 中修改 PS3,PS4 的值(上一步已经知道 PS1,PS2 无法继承了):
- 12:21[root@centos7 /data/scriptest]# PS3="Please input"
- 12:22[root@centos7 /data/scriptest]# PS4="==="
- 12:22[root@centos7 /data/scriptest]# declare -x
- declare -x PS1="\\[\\033[01;35m\\]\\A[\\u@\\h \\w]\\\$\\[\\033[00m\\]"
- declare -x PS2=">"
- declare -x PS3="Please input"
- declare -x PS4="==="
最后再次以脚本方式也就是子 shell 方式执行此文件(脚本):
可见 PS3 可以直接继承, 并且在 select 中生效了, 而 PS4 并没有继承, 还是原先的值.
- 12:22[root@centos7 /data/scriptest]# PS1test.sh
- 12:24[root@centos7 /data/scriptest]# PStest
- PS1=
- PS2=
- PS3=Please input
- PS4=+
- 1) test1
- 2) test2
- 3) test3
- Please input3
- test3
测试过程 B: 下面是测试可以被子 shell 直接继承的由 shell 本身默认定义的变量的一些测试过程:
先写脚本, 然后以子 shell 方式进行测试:
- 21:36[root@centos7 /data/scriptest]# cat testsource.sh -n
- 1 #!/bin/bash
- 2 echo PATH=$PATH
- 3 echo PWD=$PWD
- 4 echo HOSTNAME=$HOSTNAME
- 5 echo HISTSIZE=$HISTSIZE
- 6 echo HISTCONTROL=$HISTCONTROL
- 7
- 21:36[root@centos7 /data/scriptest]# ./testsource.sh
- PATH=/data/App/httpdnew/bin:/data/App/cmatrix/bin:/data/App/tree/bin:/data/scriptest/:.:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/bin:/root/bin
- PWD=/data/scriptest
- HOSTNAME=centos7.6test
- HISTSIZE=1000
- HISTCONTROL=ignoreboth
crontab 的两个坑人注意点:% 和环境变量
crontab 的执行过程
它的执行过程比较特殊, 它执行的时候并不会从当前 shell 中继承各种系统定义的环境变量和自己定义的环境变量 (全局变量) 等等, 因此必须在它执行的时候传递给它各种环境变量才能保证后的命令完全正确的执行. 分情况分析:
系统定义的环境变量等等, 这些环境变量在开机 (开启 shell) 的时候会载入配置文件从而在当前 shell 中得到数值, 而这些环境变量在 crontab 中基本上都不会继承.
自己定义的一些普通变量, 比如说自己在 / etc/profile.d 文件夹中或者 / etc/profile, ~/.bashrc 等这些配置文件中定义的普通变量, 再开机 (开启 shell 后) 并已经载入内存中, 这些通通不会继承, 包括直接在命令中定义的普通变量(这些在 bash shell 中开启子 shell 都不会继承, 更别说在 crontab 中了)
自己定义的一些环境变量, 包括 2 中说的这些文件中的, 或者在命令行中直接定义的环境变量, 也不能够在 crontab 中继承
注意, 自己在命令行中定义的变量直接就存入了内存中, 下次开启 shell 就会丢失, 而文件中的下次开启 shell 不会丢失. 但需要区分环境变量和普通变量.
从上面可见 crontab 几乎不会继承任何变量, 不论是系统定义的还是自己定义的, 不论是环境还是普通变量, 不论是内存中的还是文件中的.
它也是开启了一个子 shell, 不过与 bash shell 的区别就在于环境变量不会继承. 因此为了命令的正确进行, 可有下面的比较推荐的两种解决方式:
直接在 crontab -e 中的执行频率后面, 真正要执行的命令前面, 写入引用命令: source /etc/profile && source ~/.bash_profile (这里没有写上全部的环境变量配置文件)
这个命令就是为了把这些配置文件 (包括自己定义的环境变量文件) 引用进 crontab 执行环境中去, 也就是把这些环境变量先导入, 然后再执行需要执行的命令
如果 crontab 中的命令是要执行脚本, 则在后面需要执行的脚本中添加写入 1 中的 source 引用, 然后就可以使用这些环境变量了
需要注意点:
crontab 无论如何操作都引用不了自己在当前 shell 的命令行中直接用命令 declare -x(或 export)定义的环境变量, 因为它们在内存中, 无法引用出来.(注意和 shell 的区别, 当前 shell 中开启的子脚本 (子 shell) 中可以引用它们, 前面已经分析过了)
crontab 在书写命令的时候最好要加上全局路径, 因为 PATH 这个变量默认也是没有引用的, 不过 PATH 这个变量其实默认在 / etc/crontab 中定义过, crontab 是按照这里面的定义来判断 PATH 变量的值的, 而不是从当前的 shell 中继承.
其实这个文件是可以定义一些环境变量的, 比如把 PATH 等等写进去和当前 shell 中的 PATH 相同, 这样的话 crontab 执行命令的时候就不用在写全路径了
可参照下面原本的格式 (上面定义环境变量的部分) 来写, 提前定义一些环境变量. 不过推荐还是按照上面的两种方式来解决, 因为如果环境变量变化了每次都要修改这个文件:
- 21:37[root@centos7 /data/scriptest]# cat /etc/crontab
- // 就是按照下面这 3 行的格式来定义自己需要的环境变量
- SHELL=/bin/bash
- PATH=/sbin:/bin:/usr/sbin:/usr/bin
- MAILTO=root
- # For details see man 4 crontabs
- # Example of job definition:
- # .---------------- minute (0 - 59)
- # | .------------- hour (0 - 23)
- # | | .---------- day of month (1 - 31)
- # | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
- # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
- # | | | | |
- # * * * * * user-name command to be executed
source 的简单说明
source 的命令其实很简单, 就相当于是在当前的 shell 中执行文件中的命令(把文件中的每一行命令拉到命令行来执行), 类似于函数, 因此它能够改变当前 shell 的环境变量等等.
这也是为何我们用 source 来进行配置文件 (尤其是环境变量) 的修改之后让它生效的, 而不是用 "bash 脚本" 或者添加 PATH 和执行权限后直接执行脚本的方式来修改环境变量.
因为后两种方式修改的环境变量只能在子脚本 (shell) 中有效, 而前面说过虽然子脚本能继承环境变量(除了那些特殊的比如 PS1, 就算父 shell 修改 PS1 定义为环境变量, 当开启子 shell 后它在子 shell 中也默认为空值没有定义), 但是修改这些环境变量的值并不能返回到父 shell 中, 也就实现不了使配置文件生效的目的了(其实生效了, 不过是在子 shell 中生效的, 子 shell 一旦退出所有配置便消失不能传给父 shell)
3. 问题的结论:
从上面分析得知, 不论怎样都无法在子 shell 中修改环境变量 (包括 PS1) 的值并传给父 shell, 而 crontab 默认开启子 shell, 因此它不仅改不了 PS1, 其他的环境变量也无法应用到父 shell 中, 就算用 source 命令也只是在 crontab 开启的子 shell 中应用这些环境变量, 不能修改它们传递到父 shell 也就是当前 shell 中.
同时, 我们可知在使用 crontab 的过程中, 不能写入修改当前 shell 中任何变量 (普通, 环境) 的命令, 就就算写了, 这些命令也都是无效的无法传回当前 shell 从而修改这些变量的值.
更多分析:
只有一种方法, 也就是用 crontab 修改文件的内容(文件里可以写入环境变量), 因为文件是保存在磁盘中的, 每次使用它的时候才会读入内存, 这就和 shell 无关了, 也就相当于所有的 shell 都可以查看并使用这些文件, 实现了曲线传递数据的方法.
然后退出之后在父 shell 也就是当前 shell 中执行一下 source 命令这些文件, 这样才能够实现在当前 shell 中实现环境变量的更新. 不过这样做还得手动 source 一下, 相当于无法自动配置了, crontab 也就没有意义.
不过用这种方式用来配置系统服务和一些其他程序的配置文件, 还是能够生效的(配置完之后别忘了重新读入配置文件或者重启), 当然也能进行一些备份操作等等(因为备份就是存入文件到磁盘中, 和 shell 无关)
原因就是因为 (这些程序如果配置的时候需要环境变量, 就按照上面的解决方法来导入环境变量) 在 crontab 修改它们的配置文件后重新载入或者重启, 退出 crontab 之后这些服务并不会关闭, 而此时配置文件已经读入内存, 所以也就实现了 shell 之间的服务程序的配置传递,(如果有必要还可以再加上 nohup 命令让它和终端也无关)
crontab 的 % 的说明
这个在 crontab 中代表换行, 想要使用它要么 \% 转义的方式, 要么就把它写入脚本中, 或者写在单引号中不需要转义, 不过此时就不能用于计算取余或者字符串变量操作中的一些命令了.
但是注意别忘了 % 它不能在 crontab 中直接使用
更多关于 crontab 的使用可看计划任务博客一章, 比较常用的一些:
tail /var/log/cron : 查看 cron 执行日志
cat /var/spool/cron / 用户名: 类似于 crontab -l , 查看用户名的用户定义的 crontab 命令.
来源: http://blog.51cto.com/14228129/2384429