请启用 Javascript 以查看内容

Python3设置Linux系统上的环境变量可以让程序结束后环境变量被shell命令捕获到

 ·   ·  ☕ 7 分钟  ·  ✍ ayunw

前言

越来越多的需求无法单纯的用shell来实现了,或者用shell不如用Python来实现。因为Python中的列表和字典真的是非常非常的常用以及好用。而我这里有一个需求,先从yaml或者json中获取我要的值,然后通过Python二开kubernetes(其实就是调用一些库来拿到k8s集群上我想要的东西定制化一下),获取k8s集群上我需要的数据,然后这部分数据要在GitLab CICD中配合前面从yaml或者json取到的数据做判断(当然逻辑不是这么简单,相对复杂)。那么我从yaml或者json中获取到的数据最好是用一个env的形式传进Python脚本,循环、判断结束后,然后在Linux系统上设置一个env,让GitLab CI的pipeline中获取到这个env,然后判断是否要继续pipeline还是中断这个pipeline。

方法一、通过export key=value方式设置环境变量

提示: 该方法没有用,并不会生效

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os

set_ns_env = "export NAMESPACE='paas'"
#exec_ns_env = os.getenv('NAMESPACE')

set_vhost_env = "export VHOST='sre.ayunw.cn'"
#exec_vhost_env = os.getenv('VHOST')

print(os.getenv('NAMESPACE'))
print(os.getenv('VHOST'))

# 执行脚本测试
~]# python3 set_env.py
None
None

方法二、通过向宿主机/etc/profile文件添加

提示: 该方法没有用,并不会生效

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os

add_path_env = "echo 'export ext_code=1' >> /etc/profile"
take_env_effect = "source /etc/profile"
print(os.popen(add_path_env).read())
print(os.popen(take_env_effect).read())

# 执行脚本测试
~]# python3 set_env.py


~]# cat /etc/profile
export ext_code=1
~]# echo ${ext_code}
~]# source  /etc/profile
~]# echo ${ext_code}
1

可以看到文件是追加进去了,但是却没有生效,也许是我的方式不对,不应该就os.popen?然后手动执行就生效了。

方法三、通过os.environ[‘key’] = ‘value’的方式设置环境变量

提示: Python脚本中可以获取到变量值,但是Python脚本结束后在命令行终端就无法获取到了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os

os.environ['NAMESPACE'] = 'paas'
print(os.getenv('NAMESPACE'))
# output: paas

# 脚本执行结束后,在终端就无法获取到该变量值了
~]# env | grep "NAMESPACE"
~]#

# 或者直接运行该脚本然后echo输出变量
~]# python3 set_env.py; echo $NAMESPACE
paas

可以看到执行脚本后直接echo输出变量时拿不到变量值的

原因分析

对于环境变量的设置来说,Python直接执行export NAMESPACE=paas是无法设置成功的,设置方法可以通过os.environ['NAMESPACE'] = 'paas'来设置,但是由于Python运行是启动新的进程,设置的环境变量只在该进程内有效,所以set_env.py执行完毕后也无法再获取到设置的环境变量。

解决方法

当我们直接在命令行或者shell中执行export NAMESPACE=paas是可以成功设置的,也就是说在标准输出中执行该命令是可以设置为session级别的环境变量(其实也是进程级别,在shell中在启动进程就属于该shell的子进程是可以继承父进程的环境变量),所以可以通过echo $NAMESPACE命令再次查到结果。

比如:

1
2
3
4
~]# export NAMESPACE="paas"
~]# echo $NAMESPACE
paas
~]# 

所以,如果我们要通过Python脚本来设置Linux上的环境变量,并且可以让通过Python脚本设置的环境变量在Linux系统上能被shell命令拿到,那么我们就可以将export的命令重定向到标准输出即可,在Python中最简单的重定向到标准输出就是print了。

方法四、更改脚本,实现Python设置Linux上的环境变量

提示: 这个方法能够做到,但是当你Python脚本中逻辑复杂,有很多变量、注释、调用了操作系统上的命令以及有很多print等,那么则可能会出现误判。因此那种复杂场景不是很建议用这种方式。

先简单说一下eval命令:

格式:eval command-line

command-line就是在终端上输入的一条命令。但是在这条命令的前面加了一个eval命令时,他的结果就是shell在执行命令之前扫描它两次。

eval参考

例子1:

1
2
$ pipe='|'
$ eval ls -al $pipe wc -l

解释:这里第一次进行扫描时,会替换出pipe的值,也就是管道符"|",然后eval再次扫描命令行,这时候shell就把 | 作为了管道符进行命令执行。

也就是说:如果变量中包含任何需要shell直接在命令行中看到的字符(不是替换的结果),就可以使用eval。命令行结束符(;| &),I/o重定向符(< >)和引号就属于对shell具有特殊意义的符号,必须直接出现在命令行中。

例子2:

1
2
3
4
5
6
7
$ cat param.sh
#!/bin/bash

eval echo \$$#

$ ./param.sh allen jol 18
18

可以看到,第一遍扫描后,shell把反斜杠去掉了。当shell再次扫描该行时,它替换了$3的值,并执行echo命令,输出了18

再来说我们的原先的话题,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
from pipes import quote

def custom_env():
    cus_ns_env = "export NAMESPACE={};".format(quote("paas"))
    cus_vhost_env = "export VHOST={};".format(quote("sre.ayunw.cn"))
    print(cus_ns_env)
    print(cus_vhost_env)

    #print("export NAMESPACE={};".format(quote("paas")))
    #print("export VHOST={};".format(quote("sre.ayunw.cn")))

    return cus_ns_env, cus_vhost_env

custom_env()

# 执行脚本,查看结果
~]# eval $(python3 set_env.py)
~]# echo $NAMESPACE
paas
~]# echo $VHOST
sre.ayunw.cn

~]# env | grep NAMESPACE
NAMESPACE=paas
~]# env | grep VHOST
VHOST=sre.ayunw.cn

可以看到,在方法四种,我们的需求就实现了。
那么针对我的需求,我就可以用这种方式在Linux系统上设置一个环境变量,然后执行脚本结束后,捕获到我需要的变量,然后判断变量就可以知道是否需要去强制中断我的pipeline还是让pipeline继续运行了。

但是这里会有一个问题,就是用了eval命令。eval命令会两次解析脚本,如果脚本中有过多且过于复杂的变量,那么很有可能误操作,因此这种需求下还是要慎用这个方式。

方法五、用shell获得Python的print的值(类似方法四)

和方法四有点类似,也是想要通过shell来获取Python的print的值。如果打印1,表示我要退出pipeline,如果打印为0,则表示正常执行pipeline,但是这个也会有一定的问题。如下:

1
2
3
4
5
6
7
8
9
~]# cat test.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

print('1')

~]# testVar=`python3 test.py`
~]# echo $testVar
1

这种情况下是没有问题的,但是你要考虑另一种问题,因为可能存在很多次循环,如果第一次循环打印1,第二次循环打印了0,然后循环结束了,最终打印了1又打印了0。那这个怎么算?算最后一次的0那就不会中断pipeline了,那算第一次的1?这种明显是有逻辑问题。正常来说我们获取到Python中打印的1就是要告诉pipeline退出流水线的。

如下:这是我实战的脚本,但是里面就是因为存在多次循环,都拿到了1,所以这里赋值给变量的时候出现了两个1,此时shell判断就产生了误判,变成了nothing to do!,而实际上他是1,应该退出pipeline的。

1
2
3
4
5
6
~]# export CI_PROJECT_NAMESPACE='ommp'
~]# code=`python3 vs_1.0.6.py`
~]# echo $code
1 1
~]#  if [ "$code" = "1" ];then echo "exit code is [$code], will exit!";else echo 'nothing to do!';fi
nothing to do!

因此这个方式不是很好,适用于只有一个返回值的情况,才不会误判。

方法六、终极大招,用退出码 sys.exit()

前面的5种方法都不行,那难道没办法了吗?这个需求做不了了?其实可以换一种思路,我们使用退出码来作为信号标识。

执行sys.exit()语句会直接退出程序,这也是经常使用的方法,也不需要考虑平台等因素的影响,一般是退出Python程序的首选方法。该方法中包含一个参数status,默认为0,表示正常退出,也可以为1,表示异常退出。

注意: 要先在Python脚本中import sys导入模块才能使用sys.exit()哦!

例子如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
~]# export CI_PROJECT_NAMESPACE='paas'
~]# python3 vs_1.0.6.py
Now, check deploy,wait for a few minutes!

~]# echo $?
0

~]# export CI_PROJECT_NAMESPACE='ommp'
~]# python3 vs_1.0.6.py
Now, check deploy,wait for a few minutes!

This service namespace is: ommp, domain is: allenjol.pre.ayunw.cn. Can Not Deploy This Service...
~]# echo $?
1

以上方法六可知,通过echo $?这样就拿到了它的退出状态码。当退出码为1时,表示我要让整个pipeline退出了。为0,则什么都不做,继续执行pipeline。

此时通过方法六,我们的需求就达到了!这也是目前来说一个比较好的方式!

退出GitLab CICD的pipeline的方式:在stage中使用 exit 1即可,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
deploy:
  stage: deploy
  image: registry.ayunw.cn/paas/kubernetes-deploy:20220428:1-a93e59b9
  tags:
    - default
  script:
    - env
    - python3 vs_1.0.6.py
    - if [ "$?" -ne 0 ]; then exit $?; fi
...
                                    ----- 本页内容已结束,喜欢请分享并注明原文链接 -----
您的鼓励是我最大的动力
alipay QR Code
wechat QR Code

Avatar
作者
ayunw
尼古丁的绑架没有救赎,我们皆是上瘾的囚徒


目录