又一个坑,Linux中~(tilde)和$HOME的区别

一个sratoolkit的报错

想到Linux中~$HOME的区别这个问题,是因为在集群上安装了sratoolkit,在使用fastq-dump时遇到的一个报错:

$ fastq-dump --split-3 A549-DRR016695.1
2019-11-30T16:49:38 fastq-dump.2.9.6 err: path not found while searching dynamic library within file system module - failed to open SRA manager

=============================================================
An error occurred during processing.
A report was generated into the file '/work/xxxxx/ncbi_error_report.xml'.
If the problem persists, you may consider sending the file
to 'sra-tools@ncbi.nlm.nih.gov' for assistance.
=============================================================

同样fasterq-dump也无法正常使用:

$ fasterq-dump --split-3 A549-DRR016695.1
2019-11-30T16:50:19 fasterq-dump.2.9.6 err: cmn_iter.c cmn_get_acc_type( 'A549-DRR016695.1' ).VDBManagerMakeRead() -> RC(rcFS,rcDylib,rcSearching,rcPath,rcNotFound)

排查过程:

  1. 百度和Google搜索没有找到解决方案;
  2. GitHub上搜索关键词没有找到相关issue,看了一眼报错的源代码,但没有看懂;
  3. 发现本地安装的WSL上sratoolkit运行正常;
  4. 因此怀疑安装包或者sra文件损坏,但是用md5sum检查之后并没有问题;
  5. 发现用相对路径或者绝对路径运行fastq-dumpfasterq-dump都可以正常使用,但通过环境变量调用不能正常运行;
  6. 比较了本地和集群.bashrc里sratoolkit环境变量的写法,区别是本地用的完整路径,集群上用的~
  7. 修改集群.bashrc环境变量写法,source之后重新测试,问题解决。

总结下来就是在.bashrc中用~代替完整路径导致的一个问题。可能大约一年以前,我都一直认为~$HOME两者是等效的,直到在win10上安装了WSL (Windows Subsystem for Linux)并把软件安装到了$HOME之后才发现两者还是存在一些区别(平时在公司有专门的共享目录安装软件)。具体表现如下:

如果在.bashrc中这么写:

export PATH='~/software/sratoolkit.2.9.6-1-centos_linux64/bin/':$PATH

source ~/.bashrc之后运行which fastq-dump将不会返回任何值。但是如果在.bashrc中写完整路径:

export PATH='/home/xiaofei/software/sratoolkit.2.9.6-1-centos_linux64/bin/':$PATH

which将正确返回fastq-dump的路径:

$ which fastq-dump
/home/xiaofei/software/sratoolkit.2.9.6-1-centos_linux64/bin//fastq-dump

接下来我又尝试了在.bashrc中用$HOME替代/home/xiaofei,发现两者是等效的(至少在这里运行fastq-dumpwhich时结果是一致的)。我在其他服务器上用which从来没有遇到过这个问题,所以我一度以为是WSL或者Ubuntu的bug,直到今天在学校的集群上遇到这个sratoolkit的报错。

~$HOME到底有什么区别?

我查阅了GNU文档 - Tilde Expansion中介绍~和由它构成的"tilde-prefix"在shell中是如何被处理的,直接用代码举例解释:

# 1.没有引号或者任何字符在`~`之前的时候,才构成所谓"tilde-prefix"
$ echo "~"
~ # "~"前有引号
$ echo test~
test~ # "~"前有其他字符串
# 2. 这个"tilde-prefix"中没有任何字符被引号包围的时候,"~"后的字符被当作可能的登录名
$ echo ~"test"
~test # 由于后面"test"被引号包围,所以"~"被当作字符串处理
# 3. 如果登录名是空字符串,"~"被替换为shell变量$HOME
$ echo ~
/home/xiaofei
$ echo ~/foo
/home/xiaofei/foo
# 4. 否则"tilde-prefix"被替换为特定登录名的$HOME
$ echo ~root/foo
/root/foo # ~root被当作用户root的home目录
$ echo ~xiaofei/foo
/home/xiaofei/foo # ~xiaofei被当作用户xiaofei的home目录
$ echo ~test
~test # 由于"test"用户名不存在,因此"~"被当作字符串处理
# 5. 其他一些用法
$ pwd
/home/xiaofei/test # 当前目录
$ echo ~+/foo
/home/xiaofei/test/foo # "~+"被替换为当前目录
$ echo ~-/foo
/home/xiaofei/foo # "~-"被替换为上一次所在目录

虽然该文档中也提到,在指定$PATH等环境变量时用~是OK的:

Each variable assignment is checked for unquoted tilde-prefixes immediately following a ‘:’ or the first ‘=’. In these cases, tilde expansion is also performed. Consequently, one may use filenames with tildes in assignments to PATH, MAILPATH, and CDPATH, and the shell assigns the expanded value.

但是从上文sratoolkit和which中发现的问题来看,~在读入另一个程序中进行处理的时候,依旧可能出现问题,不够稳健。因此小伙伴们以后在写环境变量的时候,尽量用完整的绝对路径而不要用~

最后再解释一下为何在本地安装的WSL和集群上的which对待~会有区别:我发现WLS上安装的Ubuntu(包括16.04和18.04)用的/usr/bin/which都是通过一个shell脚本实现的(我不确定原生的Ubuntu是不是这样);而集群安装的CentOS,用的/usr/bin/which是一个二进制可执行文件GNU which。这个shell版的which怎么产生的错误呢,大概逻辑如下:

$ a="~/test.txt" # 如果用"~"
$ b="/home/xiaofei/test.txt" # 如果用完整路径
$ if [ -f "$a" ];then echo "True";fi # which会找不到该文件
$ if [ -f "$b" ];then echo "True";fi # 用完整路径则可以
True

想必sratoolkit也会是类似的问题吧。最后再次强调,以后在写环境变量的时候,尽量用完整的绝对路径而不要用~,以免踩坑!

Python中的补充知识

在Python中,~也不能用常规方法进行解析,需要用os.path.expanduser

>>> import os
>>> os.path.isfile('~/test.txt')
False
>>> os.path.exists('~/test.txt')
False
>>> os.path.expanduser('~/test.txt')
'/home/xiaofei/test.txt'
>>> new_path = os.path.expanduser('~/test.txt')
>>> os.path.isfile(new_path)
True
>>> os.path.exists(new_path)
True

参考资料

https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html

标签: Linux, Shell, Python

知识共享许可协议 作者: 链接:https://byteofbio.com/archives/19.html
本文采用“署名-非商业性使用-相同方式分享 4.0 国际许可协议”进行许可

暂无评论

添加新评论