Linux的PS1.PS2.PS3.PS4等环境变量;Crontab的两个坑人点;变量传递等

1.问题出现:

我为了实现一个功能,就是让PS1变量(命令行提示符)每隔1分钟(利用crontab计划任务)变化一次颜色和背景格式以实现酷炫的效果,但是经过了各种尝试均以失败告终。虽然能够实现让PS1每按一次回车变化一次颜色(这个有人想尝试的话下面写的有),但是无法做到让它每隔一段时间进行一次格式的变化
为了解决这个问题,进行了一些研究,总结了一下写在下面

附加:PS1每按一次回车实现颜色变化实现:

  1. 先在脚本中写入:

    #!/bin/bash
    PS1="\033[01;\$[RANDOM%7+31]m\A[\u@\h \w]\\$\033[0m " 
  2. 然后命令行直接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.以下是问题的分析和总结:

环境变量

  1. 我们知道,环境变量(全局变量)虽然能够在当前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
  1. 而定义一个函数则它便是在当前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):

  1. 自己在shell开启后的命令行中或者说在开启shell的时候载入的配置文件中定义的环境变量,只要不是下面中所说的极特殊的那些(不能被直接继承的),都能够在当前shell中开启的子shell中被继承。
  2. 系统(shell自动配置的)中常用的环境变量,在linux的bash shell中,如果再次开启子bash shell,按照分析得知应该能全部被继承(因为环境变量的特性),但测试得知并非完全如此:

而能够被这个子shell直接继承的有(基本上在开机后shell开启后用declare -x命令查看到的这些出现的变量都能够继承)

  1. PATH变量
  2. PATH
  3. PWD
  4. HOSTNAME
  5. HISTSIZE
  6. HISTCONTROL
  7. PS3:
    • 它是命令select后选择时出现的选择提示符,默认是没有定义的空字符,且默认不是环境变量,此时会显示#?。
    • 经过测试把它定义为环境变量并赋值之后,在子shell中能够直接继承父shell的PS3

不能够被直接继承的有

  1. PS1:

    • 它就是命令行的提示符的值,可以有很多格式,具体查看帮助man bash.
    • 它默认不是一个环境变量,但是就算用命令declare -x把它定义为了环境变量,子shell也无法继承,子shell中经过测试为空;
    • 由此可见这个PS1应该是一个特殊变量,这也侧面解释了bash shell开启的时候默认没把它定义为环境变量,下面几个PS同理
  2. PS2:

    • 同上,默认不是环境变量,定义为环境变量之后也无法继承;
    • 它是一个非常长的命令可以通过在末尾加“\”使其分行显示后的多行命令的默认提示符,默认已经定义为普通变量,值为"> "
  3. PS4:

    • 同上
    • 它就是set -x命令用来修改跟踪输出的前缀,默认定义为普通变量且值为"+ "
  4. 更多的其他还没测试,以后补充,不过从1中可见有些特殊变量就算定义为了全局变量,在子shell开启的时候也会把它覆盖掉从而无法继承(相当于在shell开启过程是中重新声明定义了这些变量,这个就是开启shell时的内部逻辑了)
    • 注意,以上说的将PS定义为环境变量都是开启shell时之后的操作,只是存入内存中了,如果另开新的shell,这些操作都会失效,恢复到默认的shell设置
    • 同时我们可以想到,只要能找到定义PS1,PS2,PS3,PS4的配置文件位置(shell开启时),并将它修改为自己想要的值,(就比如上面的PS1每次都改变的命令),这样每次开启一个新的shell,就算这些环境变量不能继承,但是按照shell开启时载入的配置文件中写的这些特殊的环境变量默认设置,就能部分实现自己想要的设置。不过更方便的方法还是直接在子shell中source一个配置文件即可,这个配置文件中写上这些环境变量的赋值即可,唯一的缺憾就是不能修改后传递给父shell(这部分不理解先把下面的分析看完再回头看)。

测试过程A:PS的继承变量的测试(可先把下面的分析看完再回头看):

  1. 首先新开一个终端(也就是新开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
  1. 在当前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]# 
  1. 在当前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="+ "
  1. 先在子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]# 
  1. 然后在当前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="=== "
  1. 最后再次以脚本方式也就是子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中继承各种系统定义的环境变量和自己定义的环境变量(全局变量)等等,因此必须在它执行的时候传递给它各种环境变量才能保证后的命令完全正确的执行。分情况分析:

  1. 系统定义的环境变量等等,这些环境变量在开机(开启shell)的时候会载入配置文件从而在当前shell中得到数值,而这些环境变量在crontab中基本上都不会继承。
  2. 自己定义的一些普通变量,比如说自己在/etc/profile.d文件夹中或者/etc/profile, ~/.bashrc等这些配置文件中定义的普通变量,再开机(开启shell后)并已经载入内存中,这些通通不会继承,包括直接在命令中定义的普通变量(这些在bash shell中开启子shell都不会继承,更别说在crontab中了)
  3. 自己定义的一些环境变量,包括2中说的这些文件中的,或者在命令行中直接定义的环境变量,也不能够在crontab中继承
  • 注意,自己在命令行中定义的变量直接就存入了内存中,下次开启shell就会丢失,而文件中的下次开启shell不会丢失。但需要区分环境变量和普通变量。

从上面可见crontab几乎不会继承任何变量,不论是系统定义的还是自己定义的,不论是环境还是普通变量,不论是内存中的还是文件中的。
它也是开启了一个子shell,不过与bash shell的区别就在于环境变量不会继承。因此为了命令的正确进行,可有下面的比较推荐的两种解决方式:

  1. 直接在crontab -e中的执行频率后面,真正要执行的命令前面,写入引用命令: source /etc/profile && source ~/.bash_profile (这里没有写上全部的环境变量配置文件)
    • 这个命令就是为了把这些配置文件(包括自己定义的环境变量文件)引用进crontab执行环境中去,也就是把这些环境变量先导入,然后再执行需要执行的命令
  2. 如果crontab中的命令是要执行脚本,则在后面需要执行的脚本中添加写入1中的source引用,然后就可以使用这些环境变量了

需要注意点:

  1. crontab 无论如何操作都引用不了自己在当前shell的命令行中直接用命令declare -x(或export)定义的环境变量,因为它们在内存中,无法引用出来。(注意和shell的区别,当前shell中开启的子脚本(子shell)中可以引用它们,前面已经分析过了)
  2. 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命令。
上一篇:nrm : 无法加载文件 C:\Program Files\nodejs\nrm.ps1,因为在此系统上禁止运行脚本。


下一篇:linux系统杂项(目录、输出信息含义、crontab定时任务、修改命令行颜色、通配符、软连接 硬链接 、rpm包的命名 )