为什么在提供文件路径时我不能使用“~”代替“/home/username/”?

我可以使用~代替/home/username/来指向文件路径,例如在解压.zip文件时。
然而,今天当我按照同样的方式在终端中运行一个RNN示例时,抛出了tensorflow.python.framework.errors_impl.NotFoundError错误。
$ python ptb_word_lm.py --data_path=~/anaconda2/lib/python2.7/site-packages/tensorflow/models-master/tutorials/rnn/simple-examples/data/ --model=small 
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcublas.so.8.0 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcudnn.so.5 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcufft.so.8.0 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcuda.so.1 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcurand.so.8.0 locally
Traceback (most recent call last):
  File "ptb_word_lm.py", line 374, in <module>
    tf.app.run()
  File "/home/hok/anaconda2/lib/python2.7/site-packages/tensorflow/python/platform/app.py", line 44, in run
    _sys.exit(main(_sys.argv[:1] + flags_passthrough))
  File "ptb_word_lm.py", line 321, in main
    raw_data = reader.ptb_raw_data(FLAGS.data_path)
  File "/home/hok/anaconda2/lib/python2.7/site-packages/tensorflow/models-master/tutorials/rnn/ptb/reader.py", line 73, in ptb_raw_data
    word_to_id = _build_vocab(train_path)
  File "/home/hok/anaconda2/lib/python2.7/site-packages/tensorflow/models-master/tutorials/rnn/ptb/reader.py", line 34, in _build_vocab
    data = _read_words(filename)
  File "/home/hok/anaconda2/lib/python2.7/site-packages/tensorflow/models-master/tutorials/rnn/ptb/reader.py", line 30, in _read_words
    return f.read().decode("utf-8").replace("\n", "<eos>").split()
  File "/home/hok/anaconda2/lib/python2.7/site-packages/tensorflow/python/lib/io/file_io.py", line 106, in read
    self._preread_check()
  File "/home/hok/anaconda2/lib/python2.7/site-packages/tensorflow/python/lib/io/file_io.py", line 73, in _preread_check
    compat.as_bytes(self.__name), 1024 * 512, status)
  File "/home/hok/anaconda2/lib/python2.7/contextlib.py", line 24, in __exit__
    self.gen.next()
  File "/home/hok/anaconda2/lib/python2.7/site-packages/tensorflow/python/framework/errors_impl.py", line 469, in raise_exception_on_not_ok_status
    pywrap_tensorflow.TF_GetCode(status))
tensorflow.python.framework.errors_impl.NotFoundError: ~/anaconda2/lib/python2.7/site-packages/tensorflow/models-master/tutorials/rnn/simple-examples/data/ptb.train.txt

然后我用/home/username/替换了~,这样就正常工作了。
为什么在运行RNN示例时,我不能使用~来指向文件路径呢?
你能详细告诉我吗?

http://stackoverflow.com/questions/1685894/home-directory-expansion-within-an-argument - muru
~ 是否始终等于 $HOME? - Stéphane Chazelas
@OskarSkog 在参数传递给Python之前,Shell不应该展开~吗?就像Shell会展开路径中的反斜杠转义符或者在路径被引号引用时移除引号一样。 - micheal65536
2$VARIABLES不同,~只在字符串的开头进行扩展。 - alexis
@OskarSkog,“Python does not know what ~ means” 意味着问题是特定于 Python 缺乏某个功能的,这样设定了一个不合理的期望,即在 UNIX 工具中执行 exec 后进行扩展的功能应该是普遍可用的。 - Charles Duffy
@OskarSkog,同样地,“在Python中,好像所有东西都用单引号引用”这种说法是不正确的,如果这是与其他任何工具进行比较的话。echo ~python ~将以完全相同的方式展开参数,而echo --foo=~python --foo=~将以完全相同的方式失败展开,但“好像所有东西都用单引号引用”似乎表明Python会抑制在调用前本应发生的shell展开行为。 - Charles Duffy
@OskarSkog,...所以我真的不知道你的说法怎么可能被解读为Python在某种程度上对这种行为负有责任。(对于从Windows转过来的人来说,在字符串到参数向量的命令行解析是由调用的命令而不是外壳进行调用,行为差异在每个可执行文件的基础上甚至可以有意义!) - Charles Duffy
2@phuclv 这里不执行波浪线扩展的原因与引用无关,所以我认为这不是一个关于引用如何抑制波浪线扩展的重复问题。有一些内容会回答这些问题,但我不认为它们符合重复的条件。 - Eliah Kagan
4个回答

你需要明白,在shell中,~通常会被展开;你调用的程序并不会看到它,它们只会看到由bash插入的完整路径名。但这仅在波浪号位于参数的开头(且未被引用)时才会发生。
如果你运行的Python程序使用像getopt这样的模块来解析命令行,你可以将--data-path选项的参数作为一个独立的“单词”来允许波浪号展开。
$ python ptb_word_lm.py --data_path ~/anaconda2/lib/python2.7/...

在你自己的代码中,你可以使用 getopt 或 argparse 来处理参数,并且还可以像 @JacobVlijm 的回答建议的那样手动展开波浪线。
附注:波浪线也会在类似于 DIRNAME=~/anaconda2 这样的 shell 变量赋值表达式的开头进行展开;虽然你问题中的波浪线也跟在等号后面,但这种用法对于 shell 来说没有特殊含义(它只是传递给程序的内容),并且不会触发展开。

7除非你已经熟悉getopt,否则在编写Python时请使用argparse - Nick T
我在答案中添加了argparse,因为它是主要的替代方案,但个人而言,我发现它比getopt更难使用,而不是更容易。可能因人而异。 - alexis

Python中的波浪线展开

答案很简单:

除非你使用以下方法,否则python不会展开~

import os
os.path.expanduser('~/your_directory')

另请参阅此处

os.path.expanduser(path)
在Unix和Windows上,返回参数,并将~或~user的初始组件替换为该用户的主目录。

在Unix上,如果设置了环境变量HOME,则将初始的~替换为它;否则通过内置模块pwd在密码目录中查找当前用户的主目录。直接在密码目录中查找初始的~user。


12一般来说,你不应该假设波浪线扩展是在操作系统层面完成的,这是Unix shell(并非所有的Unix shell都支持!)为你做的事情。 - farsil
1我认为更相关的问题在Alexis的回答中已经解释清楚了:波浪线~在shell参数列表中的位置。 - David Foerster
@farsil,我不同意。程序可以制作为便携式版本,但当你从命令行运行它们时,是在特定的系统上运行。别忘了这是 askubuntu.com,而 Ubuntu 一直都是 Unix(就我们所知 :-) - alexis
1@alexis:Ubuntu在操作系统层面也不支持波浪线扩展。这仍然是Shell功能。 - user2357112
1我觉得你在纠缠细节。没人说内核在做这件事。关键是,它不是由接收参数的程序完成的。 - alexis
我同意David的观点:虽然在Python中,答案是正确的,但关键问题在于shell对波浪号扩展的处理(或者更准确地说,由于波浪号的位置,在OP的情况下没有进行扩展)。现在,还应该指出来的是,根据Jacob提供的文档引用,程序可以执行波浪号扩展,并且在HOME变量未设置时会非常有用(这可能是一个罕见的情况,但是有可能发生)。此外,虽然这是Ask Ubuntu网站,我们可以安全地假设是Unix-like环境,但我认为应更加重视可移植性和符合posix标准。 - Sergiy Kolodyazhnyy
Posix合规性,当然可以。我并不是要为Ubuntu独有的功能辩论,只是认为假设Unix系统是可以的。而波浪线扩展并不是Windows的特性,所以没有期望它在那里能够工作。 - alexis
@alexis:你对于“你永远不应该假设波浪线扩展是在操作系统层面完成的”这个说法表示了“我不同意”。这听起来确实像是你在说操作系统会执行这个操作。 - user2357112

波浪线扩展只在一些上下文中执行,这些上下文在不同的shell之间略有不同

它在以下情况下执行:

var=~

或者

export var=~

在一些外壳中。它不在里面。
echo var=~
env var=~ cmd
./configure --prefix=~

在POSIX shell中。
当不处于POSIX一致模式时(例如在调用为sh时,或者环境变量中设置了POSIXLY_CORRECT),它是在bash中的。
$ bash -c 'echo a=~'
a=/home/stephane
$ POSIXLY_CORRECT= bash -c 'echo a=~'
a=~
$ SHELLOPTS=posix bash -c 'echo a=~'
a=~
$ (exec -a sh bash -c 'echo a=~')
a=~

然而,这仅限于在=左侧的内容形式类似于未引用的有效变量名时才会发生。因此,在cmd prefix=~中,它会被展开,但在cmd --prefix=~(因为--prefix不是有效的变量名)以及cmd "p"refix=~(因为有引号的p)和var=prefix; cmd $var=~中则不会被展开。
zsh中,您可以设置magic_equal_subst选项,以便在任何未引用的=之后展开~
$ zsh -c 'echo a=~'
a=~
$ zsh -o magic_equal_subst -c 'echo a=~'
a=/home/stephane
$ zsh -o magic_equal_subst -c 'echo --a=~'
--a=/home/stephane

~的情况下(与~user相对),你可以直接使用$HOME代替。
cmd --whatever="$HOME/whatever"

~会展开为$HOME的值。如果$HOME未设置,不同的shell行为也会有所不同。一些shell会查询用户数据库。如果你想考虑到这一点,你可以这样做(对于~user也是如此):

dir=~ # or dir=~user
cmd --whatever="$dir/whatever"

无论如何,在除 zsh 以外的 shell 中,记得需要引用变量展开!


1Bash的参考手册似乎表明波浪号只在变量赋值和单词开头时扩展,因此在echo a=~中扩展它似乎与手册相矛盾。 - ilkkachu
@ilkkachu,是的,该手册不完整。它也没有明确指出“~”将在什么上下文中展开(“word”指的是什么)。有关更多详细信息,请参见答案顶部的链接。 - Stéphane Chazelas

~ 有特定的展开规则,而您的命令不符合这些规则。具体来说,在未引用时才会展开,可以是在单词开头(例如,python ~/script.py)或者变量赋值的开头(例如,PYTHONPATH=~/scripts python script.py)。您所写的是 --data_path=~/blabla,在Shell术语中它被视为一个单词,因此不会进行展开。

一种即时修复的方法是使用 $HOME Shell 变量,它遵循常规的变量展开规则:

python ptb_word_lm.py --data_path=$HOME/blabla

1这有点过于简化了,还有其他情况下会进行波浪线扩展,比如在 PATH=$PATH:~/bin 中。而且在除了 zsh 之外的 shell 中,需要对 $HOME 进行引用或者应用 split+glob。 - Stéphane Chazelas
@sch 抱歉,但是你在评论中提供的链接只是关于光学鼠标的问题,并没有提到波浪线展开。你能解释一下吗? - Sergiy Kolodyazhnyy
好答案。它基本上总结了bash手册中的Tilde Expansion部分所述内容。+1 - Sergiy Kolodyazhnyy
1抱歉,我在unix.SE上非常习惯使用类似[link](/a/146697)的站内链接,以至于没有意识到我们现在是在一个不同的网站上。链接应该是指向这里的。 - Stéphane Chazelas