在程序设计和系统管理中,经常会遇到两个路径概念:一个是当前工作路径,一个是可执行文件所在路径。这里的可执行文件包括可执行程序,脚本程序等等,本文中将这类路径统称为脚本路径。
利用cd或者pwd可以很容易获得当前工作路径,而脚本路径由于操作系统引入了Path搜索机制,有时并不容易确定。在Linux下,用户可以使用which, locate等命令确定可执行文件的位置,在Windows下,可以使用where命令。
之所以会有工作路径与脚本路径的差别,是由于操作系统中存在搜索路径这一机制。例如,将可执行文件的路径加入PATH环境变量,之后在命令行下运行程序就不需要输入程序的绝对路径了,shell会根据PATH环境变量的值在相应的路径中搜索,匹配相应的可执行程序并执行。
这样给用户带来了便利,但同时也带来了一些问题。绝对路径虽然繁琐,但却具有唯一性。而相对路径则具有动态性,若使用相对路径,则系统会自动将其与当前工作路径连接,得到文件的绝对路径。随着相对的基准发生改变,相对路径对应的绝对路径是不同的。例如,.
代表当前工作目录,假设其当前对应的绝对路径为C:\Users\Spirit\Desktop
。执行cd ..
切换路径到上一级目录后,.
对应的绝对路径变为C:\Users\Spirit
。
通常,这两个概念对于经常在命令行下工作的用户和程序员比较重要。本文中,我们对这两个概念做一番辨析,搞清楚其中的区别和联系,尤其是希望对大家能够对脚本路径这一概念加深理解。
工作路径是从是从调用者(caller)的角度说的;而脚本路径是从被调用者(callee)角度说的。
工作路径(Working Directory),即用户当前所在路径,可以通过以下命令得到用户工作路径:
- pwd
该命令的意思就是print working directory
,就是打印当前工作目录。 - cd
该命令用于切换工作目录,即change directory
。若不带参数,该命令也会返回当前工作路径。
有时候,程序需要相对于程序自身所在的路径进行定位,而不是通常情况下的基于当前工作路径进行定位,这就涉及到脚本路径的概念。
脚本路径,通俗来说就是可执行文件所在的路径。有些情况下,工作路径和脚本路径是相同的,比如,可执行文件就放在当前目录(即工作目录)下。但是在大部分情况下,这两个路径是不同的。对于Windows用户而言,在命令行下任何位置,输入mspaint
都可以打开Windows画图程序,但是该程序的脚本路径为C:\Windows\System32\mspaint.exe
,这是这个文件的真实位置,但是在其他位置也能调用这个程序。这是因为C:\Windows\System32
这个路径默认已经加入到系统PATH环境变量中。
为了让大家对脚本路径引起足够的重视,我们考虑下面这个例子。
#include <stdio.h>
int main(int argc, char const *argv[]) {
...
FILE *fp = fopen("data/iris.csv", "r");
...
fclose(fp);
return 0;
}
程序需要读取data文件夹下的iris.csv文件,注意这里使用的是相对路径:data/iris.csv等价于./data/iris.csv,进一步的,由于.代表当前工作路径,所以这个路径是跟调用者实际所处的路径相关的。这意味着调用者需要确保当前工作路径下面存在data文件夹,并且data文件夹里面存在iris.csv文件,否则程序就会因为找不到iris.csv文件而报错:”No such file or directory”。例如,若当前工作路径为C:\Users\Spirit\Desktop,则iris.csv文件的绝对路径为C:\Users\Spirit\Desktop\data\iris.csv,程序实际上会到这一路径读取iris.csv文件,若找不到则会报错。若用户切换工作路径到C:\Users\Spirit,则如果C:\Users\Spirit\data\iris.csv不存在,程序也会报错。有时候,这并非程序员的本意,由于上述原因导致的”No such file or directory”会让他们感到莫名其妙。他们期望的往往是让程序读取程序所在目录的data子目录下面的iris.csv文件,他们反复确认这一文件确实存在,但是程序仍然一次次报错,可能偶尔某些情况下正常(当然,这是可执行程序正好在当前工作路径的情况)。这其实就是由于他们没有理解工作路径和脚本路径的细微区别而造成的。
如果需要程序读取程序所在目录下面的data/iris.csv文件,以上代码需要做少量修改,以使程序使用脚本路径而不是默认的工作路径。
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[]) {
...
char path[MAX_PATH+1] = {0};
strncpy(path, argv[0], MAX_PATH);
path[strlen(argv[0])] = '\0';
strrchr(path, '\\')[0] = 0;
strcat(path, "/data/iris.csv");
FILE *fp = fopen(path, "r");
...
fclose(fp);
return 0;
}
上面的代码中有很大一部分是在尝试获取当前可执行程序所在的路径信息,在C/C++中,argv[0]正好保存了这一信息,因而在这里正好被我们利用。在特定的操作系统中还提供了特定的API用于获取当前可执行程序的路径,例如Windows下使用GetModuleFileName,Linux下则使用readlink从argv[0]中提取当前可执行文件的完整路径。
以下分别总结了常用语言中获取工作路径和脚本路径的代码片段(snippits)
获取工作路径
- C语言
#include <unistd.h>
char * getcwd(char * buf, size_t size);
- Python
import os
os.getcwd()
- Matlab/Octave
pwd
cd
- 批处理
%cd%
获取脚本路径
- C语言
可以直接从argv[0]中提取。Windows下可以使用GetModuleFileName,Linux下使用readlink
- Shell
script_path=$(cd "$(dirname "$0")"; pwd)
- Python
import os
script_path = os.path.dirname(os.path.absname(__file__));
- Cmd
set script_path=%~dp0%
- Matlab/Octave
script_path=fileparts(mfilename('fullpath'));