LinuxShell编程中source和export命令

目录

1.source命令

source命令是shell的内部命令,和. 命令相同.

注意这里的.+空格+文件名[参数],需要和./file.sh不同.

source命令格式:

bash 复制代码
source 文件名 [参数]

++在当前的shell环境中++ , 从文件名中读取并执行命令.

如果文件名不包括斜杠,则使用 PATH 变量去搜索文件. 如果Bash不是在POSIX模式 下运行, 则在$PATH中找不到后就会在当前目录中搜索.

如果提供了参数, 它们就成为执行文件名时的位置参数; 否则, 位置参数不会被改变.

返回状态是最后一个被执行命令的退出状态; 如果没有命令被执行,则返回零. 如果文件名没有找到, 或者不能读取, 返回状态就是非零值.

1.1.POSIX模式

bash 复制代码
#脚本第一行如下所示是执行在POSIX模式下
#!/bin/sh
#POSIX模式是符合POSIX规范的运行模式
#POSIX模式也可以如下指定,在脚本第一行
#!/bin/bash --posix
bash 复制代码
#脚本第一行如下所示是执行在非POSIX模式下,也可以认为是扩展模式,是POSIX的增强模式
#!/bin/bash

1.1.1.验证POSIX模式执行情况

准备文件夹如下:

properties 复制代码
└── posix_mode_test
    ├── local_cmd.sh
    ├── no_posix.sh
    └── posix.sh

local_cmd.sh是测试脚本命令,内容如下:

bash 复制代码
#!/bin/sh
echo "test echo"

posix.sh是测试posix模式下source命令的测试脚本

bash 复制代码
#!/bin/sh
#posix模式,文件名不包括斜杠,则使用PATH变量搜索文件,
#没有搜索到,发生错误,在posix模式中就会直接退出,不再执行接下来的命令

source local_cmd.sh

echo "return value=$#"

运行结果:

bash 复制代码
./posix.sh 
./posix.sh: line 5: source: local_cmd.sh: file not found

可以看到运行到第5行发生错误就退出了.

no_posix.sh是非posix模式下source命令的测试脚本

bash 复制代码
#!/bin/bash
#非posix模式,文件名不包括斜杠,则使用PATH变量搜索文件,没有搜索到,
#就在当前目录中搜索,在当前目录中可以搜索到local_cmd.sh文件,执行文件,显示返回值并退出
   
source local_cmd.sh
    
echo "return value=$#"

运行结果:

bash 复制代码
./no_posix.sh 
test echo
return value=0

可以看到运行结果和预期一样.

其他情况验证:

bash 复制代码
#1.第一行如下语句,也是posix模式
#!/bin/bash --posix

#2.第一行没有指定"#!/bin/sh"或"#!/bin/bash",默认情况下是非posix模式

1.2.source命令表示形式的历史由来

source filename表示形式来自Cshell;

. filename表示形式来自Bourne Shell.

为了兼容用户的习惯,使用了两种表示形式.

1.3.source命令解读

++在当前的shell环境中++ , 从文件名中读取并执行命令.

这句话很重要,包含有几层意思,接下来一一解读.

1.3.1.在当前的shell环境中

要想理解这个条件,先必须了解几种常用的shell脚本执行方式:

1.工作目录执行

先进入到脚本所在的目录(此时, 称为工作目录), 然后使用 ./脚本方式执行.

脚本需要具有执行权限才可以.

2.绝对路径执行

bash 复制代码
/home/shell/source_cmd/posix_mode_test/local_cmd.sh

也需要脚本具有可执行权限.

3.使用sh或bash命令执行

bash 复制代码
sh local_cmd.sh
#或者
bash local_cmd.sh

脚本只需要具有读权限即可,sh或bash把脚本当作参数,读取脚本内容并执行.

4.shell环境执行

也就是这里所说的**"在当前的shell环境中"**

bash 复制代码
source local_cmd.sh

上述4种shell脚本执行方式可以归为两类:

一, 1-3执行内部原理一样,会新建一个子shell,在子shell中执行脚本里面的语句,该子shell继承父shell的环境变量,但子shell新建的,改变的变量不会被带回父shell.

二,4为另一类,source命令其实只是简单地读取脚本里面的语句依次在当前shell里面执行 , 没有建立新的子shell. 那么脚本里面所有新建,改变变量的语句都会保存在当前shell里面.

验证这两类情况.

bash 复制代码
#文件目录结构
verify_shell_run/
├── child_run_test.sh
└── source_run_test.sh

child_run_test.sh 内容

bash 复制代码
#!/bin/bash

CHILD_VAR=child_context
echo "child test run!"

source_run_test.sh 内容

bash 复制代码
#!/bin/bash

PARENT_VAR=parent_context
echo "source test run!"

验证情况:

bash 复制代码
#运行第一类情况
sh child_run_test.sh
child test run!
#打印变量值
echo $CHILD_VAR
#显示内容为空

#运行第二类情况
source source_run_test.sh 
source test run!
#打印变量值
echo $PARENT_VAR
parent_context
#变量在当前shell中有效

验证结果符合预期.

1.3.2.source命令的常用用途

根据上一点分析:source命令一般的用途是,打开一个shell终端,在shell终端中执行source脚本,可以设置环境变量和函数,这些环境变量和

函数就会在当前shell终端中一直存在,建立shell终端的环境,方便工作.

Linux登录过程使用source命令建立用户环境,以下简略描述一下过程:

1.每个终端登录时,先执行/etc/profile脚本

bash 复制代码
 # /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
 # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
 
 if [ "${PS1-}" ]; then
   if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
     # The file bash.bashrc already sets the default PS1.
     # PS1='\h:\w\$ '
    if [ -f /etc/bash.bashrc ]; then
    #根据情况执行系统脚本
      . /etc/bash.bashrc
    fi  
   else
    if [ "$(id -u)" -eq 0 ]; then
      PS1='# '
    else
      PS1='$ '
    fi  
   fi  
 fi
 
  #根据情况执行系统脚本
 if [ -d /etc/profile.d ]; then
   for i in /etc/profile.d/*.sh; do
    if [ -r $i ]; then
      . $i
    fi  
   done
   unset i
 fi

 #用户可以在这里制定环境变量
 export PATH="/opt/cmake-3.27.8-linux-x86_64/bin:$PATH"
 export PATH="/opt/node-v20.11.0-linux-x64/bin:$PATH"

2.执行用户目录下的.profile脚本

bash 复制代码
 # ~/.profile: executed by the command interpreter for login shells.
 # This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
 # exists.
 # see /usr/share/doc/bash/examples/startup-files for examples.
 # the files are located in the bash-doc package.
 
 # the default umask is set in /etc/profile; for setting the umask
 # for ssh logins, install and configure the libpam-umask package.
 #umask 022
 
 # if running bash
 if [ -n "$BASH_VERSION" ]; then
     # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
    #执行用户目录下的.bashrc脚本
    . "$HOME/.bashrc"
    fi  
 fi
 
 # set PATH so it includes user's private bin if it exists
 if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
 fi
 
 # set PATH so it includes user's private bin if it exists
 if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
 fi
 
 #用户自定义设置环境变量和函数
 source $HOME/setenv_qt.sh

3...执行用户目录下的.bashrc脚本

在用户目录下的.profile脚本的第16行调用用户目录下的.bashrc脚本,如上所示.

bash 复制代码
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000

# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize

# If set, the pattern "**" used in a pathname expansion context will
# match all files and zero or more directories and subdirectories.
#shopt -s globstar

# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"

# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
    debian_chroot=$(cat /etc/debian_chroot)
fi

# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
    xterm-color|*-256color) color_prompt=yes;;
esac

# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes

if [ -n "$force_color_prompt" ]; then
    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
        # We have color support; assume it's compliant with Ecma-48
        # (ISO/IEC-6429). (Lack of such support is extremely rare, and such
        # a case would tend to support setf rather than setaf.)
        color_prompt=yes
    else
        color_prompt=
    fi
fi
if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt

# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
    ;;
*)
    ;;
esac

# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
    alias ls='ls --color=auto'
    #alias dir='dir --color=auto'
    #alias vdir='vdir --color=auto'

    alias grep='grep --color=auto'
    alias fgrep='fgrep --color=auto'
    alias egrep='egrep --color=auto'
fi

# colored GCC warnings and errors
#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'

# some more ls aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'

# Add an "alert" alias for long running commands.  Use like so:
#   sleep 10; alert
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'

# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi

# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
if ! shopt -oq posix; then
  if [ -f /usr/share/bash-completion/bash_completion ]; then
    . /usr/share/bash-completion/bash_completion
  elif [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
  fi
fi
#用户可以定制
PATH=$HOME/.local/bin:$PATH

/etc/profile,~/.profile,~/.bashrc脚本在用户登录时都会自动执行,并且设置的环境变量和函数都会在登录后保存在终端里.

如果用户修改了这三个脚本,可以不重启,不退出,使用source命令立即生效.

复制代码
source /etc/profile
source ~/.profile
source ~/.bashrc

1.3.3.从文件名中读取并执行命令

从文件名中读取并执行命令 ,可以理解为source后面接的文件名对应的文件可以不是脚本,只要是可读权限的文本文件即可.

例如,建立如下文件,只给文件可读权限

bash 复制代码
touch readme.txt

vim readme.txt
    TEST_VAR_123=hello_world
    echo "test source command!"
    
chmod a=r readme.txt
ll
-r--r--r-- 1 lei lei   53  4月 12 22:01 readme.txt

source readme.txt 
test source command!

echo $TEST_VAR_123
hello_world

2.export命令

调用格式

bash 复制代码
 export [-fn][-p][名称[=值]]

把每个名称都传到子进程的环境中.

如果给定了"-f"选项(助记词:Function函数),则名称就是shell函数;否则,它就是shell变量.

"-n"选项(助记词:No不)表示不再把名称导出到子进程.

如果没有给定名称,或者给定了"-p"选项(助记词:Pretty-Print格式化打印),则显示已经导出的名称列表.

"-p"选项能够把输出格式化成可以重新作为输入的形式.

如果名称后面是"=值"的形式,那么这个值就会赋给名称.

这个命令的返回状态是零,除非指定了无效的选项,或者其中一个名称不是有效的shell变量名,或者"-f"指定的不是一个shell函数的名称.

2.1.显示当前终端已经导出的函数和环境变量

bash 复制代码
export

... ...
declare -x LANG="en_US.UTF-8"
declare -x LC_ADDRESS="zh_CN.UTF-8"
declare -x LC_IDENTIFICATION="zh_CN.UTF-8"
declare -x LC_MEASUREMENT="zh_CN.UTF-8"
declare -x LC_MONETARY="zh_CN.UTF-8"
declare -x LC_NAME="zh_CN.UTF-8"
declare -x LC_NUMERIC="zh_CN.UTF-8"
declare -x LC_PAPER="zh_CN.UTF-8"
declare -x LC_TELEPHONE="zh_CN.UTF-8"
... ...

2.2.验证变量和函数导出功能

复制代码
export_test/
├── child_shell.sh
└── paren_shell.sh

paren_shell.sh

bash 复制代码
#!/bin/bash
PARENT_EXPORT_TEST_VAR=parent_export_value
export PARENT_EXPORT_TEST_VAR
PARENT_NOEXPORT_TEST_VAR=parent_noexport_value
function testFunc()
{
    echo "testFunc run!"
}
export -f testFunc
sh child_shell.sh

export -n PARENT_EXPORT_TEST_VAR
export -nf testFunc
sh child_shell.sh

child_shell.sh

bash 复制代码
#!/bin/bash

echo "PARENT_EXPORT_TEST_VAR=$PARENT_EXPORT_TEST_VAR"
echo "PARENT_NOEXPORT_TEST_VAR=$PARENT_NOEXPORT_TEST_VAR"
testFunc

运行结果:

bash 复制代码
sh paren_shell.sh

PARENT_EXPORT_TEST_VAR=parent_export_value
PARENT_NOEXPORT_TEST_VAR=
testFunc run!
PARENT_EXPORT_TEST_VAR=
PARENT_NOEXPORT_TEST_VAR=
child_shell.sh: line 5: testFunc: command not found

可以看到导出的变量在子shell中有值,没有导出的变量在子shell中没有值,导出的函数在子shell中可以运行.

在第二次调用子shell之前取消导出,结果变量都为空,函数没有定义.

3.source命令和export命令的结合

准备两个脚本source_parent_shell.shpri_child_shell.sh

source_parent_shell.sh

bash 复制代码
#!/bin/bash
SOURCE_PARENT_EXPORT_TEST_VAR=source_parent_export_value
export SOURCE_PARENT_EXPORT_TEST_VAR
SOURCE_PARENT_NOEXPORT_TEST_VAR=source_parent_noexport_value

pri_child_shell.sh

bash 复制代码
#!/bin/bash

echo "SOURCE_PARENT_EXPORT_TEST_VAR=$SOURCE_PARENT_EXPORT_TEST_VAR"
echo "SOURCE_PARENT_NOEXPORT_TEST_VAR=$SOURCE_PARENT_NOEXPORT_TEST_VAR"

source方式运行source_parent_shell.sh

bash 复制代码
source ./source_parent_shell.sh

在当前shell中查看变

bash 复制代码
echo $SOURCE_PARENT_EXPORT_TEST_VAR
source_parent_export_value
echo $SOURCE_PARENT_NOEXPORT_TEST_VAR 
source_parent_noexport_value

#可以看到两个变量在当前shell中都有效

使用export查看当前shell中导出的变量

bash 复制代码
export

... ...
declare -x SHELL="/bin/bash"
declare -x SHLVL="1"
declare -x SOURCE_PARENT_EXPORT_TEST_VAR="source_parent_export_value"
... ...

#可以查询到SOURCE_PARENT_EXPORT_TEST_VAR被导出
#SOURCE_PARENT_NOEXPORT_TEST_VAR没有被导出

以子进程方式运行pri_child_shell.sh

bash 复制代码
bash ./pri_child_shell.sh

SOURCE_PARENT_EXPORT_TEST_VAR=source_parent_export_value
SOURCE_PARENT_NOEXPORT_TEST_VAR=

#可以看到在子进程中SOURCE_PARENT_NOEXPORT_TEST_VAR变量不能被访问,即使在当前shell中可以访问
#一定要明确使用export导出才能在子进程中访问

4.在profile,.bashrc等脚本中变量是否需要明确导出

bash 复制代码
#在/etc/profile中定义环境变量,不导出
GLOB_PROFILE_VAR=glob_profile_value
#在~/.profile中定义环境变量,不导出
GLOB_HOME_VAR=glob_home_value

准备一个测试文件system_bash_script_test.sh

bash 复制代码
#!/bin/bash
echo "GLOB_PROFILE_VAR=$GLOB_PROFILE_VAR"
echo "GLOB_HOME_VAR=$GLOB_HOME_VAR"

以子进程方式运行

bash 复制代码
./system_bash_script_test.sh 
GLOB_PROFILE_VAR=
GLOB_HOME_VAR=

以shell环境方式执行

bash 复制代码
source ./system_bash_script_test.sh 
GLOB_PROFILE_VAR=glob_profile_value
GLOB_HOME_VAR=glob_home_value

也是只能在当前shell中访问,不能传输到子进程中,和普通脚本原理一样,没有特殊的地方.

5.结论

source命令可以改变当前shell中的环境变量,要在子shell访问必须使用export命令导出.

相关推荐
pr_note1 天前
legality检查
shell·tcl
啥都不懂的小小白2 天前
Shell脚本编程入门:从零基础到实战掌握
前端·shell
dingdingfish6 天前
GNU Parallel 学习 - 第1章:How to read this book
bash·shell·gnu·parallel
似霰9 天前
Linux Shell 脚本编程——核心基础语法
linux·shell
似霰9 天前
Linux Shell 脚本编程——脚本自动化基础
linux·自动化·shell
偷学技术的梁胖胖yo10 天前
Shell脚本中连接数据库查询数据报错 “No such file or directory“以及函数传参数组
linux·mysql·shell
纵有疾風起19 天前
【Linux 系统开发】基础开发工具详解:软件包管理器、编辑器。编译器开发实战
linux·服务器·开发语言·经验分享·bash·shell
gis分享者21 天前
Shell 脚本中如何使用 here document 实现多行文本输入? (中等)
shell·脚本·document·多行·文本输入·here
柏木乃一21 天前
基础IO(上)
linux·服务器·c语言·c++·shell
angushine22 天前
CPU脚本并远程部署
shell