我有一个文件名列表,我想为其创建软链接(称之为filenames.txt
)。使用时
cat filenames.txt | awk 'system("ln -s "$0" ~/directory\ with\ spaces/subdirectory/")`
当遇到带有空格的任何内容(文件名或目标目录)时,它似乎会卡住。
如何创建这些软链接,而不必先重命名所有内容以确保没有空格?
答案1
注意:此处提供的所有示例都将从文件和文件名列表所在的目录运行。例如,如果它们位于/mnt/Hard\ Drive/some\ files/
文件夹中,请确保filenames.txt
也存储在那里。从 shell 执行命令时,请先cd /mnt/Hard\ Drive/some\ files/
运行示例。
AWK
AWK 的系统调用可用于包含空格的项,但稍微复杂一些。您的原始代码有一些语法错误并且cat
在那里毫无用处。不管这个事实如何,正确的方法是首先将命令构建到sprintf()
变量中,然后将该变量传递给system()
.就像这样:
$ ls dir\ with\ spaces/
$ awk '{cmd=sprintf("fpath=$(realpath -e \"%s\" );%s %s \"$fpath\" \"%s\"",$0,"ln","-s","dir with spaces/");system(cmd) }' filenames.txt
$ ls dir\ with\ spaces/
ascii.txt@ disksinfo.txt@ input.txt@ process info.txt@
BASH(或任何类似 Bourne 的 shell)
阻力较小的路径是通过下面给出的小 shell 脚本:
#!/bin/bash
# uncomment set -x for debugging
#set -x
# Preferably, the directory should be full path
dir="dir with spaces/"
while IFS= read -r line
do
if [ "x$line" != "x" ] && [ -e "$line" ];
then
fpath=$( realpath -e "$line" )
ln -s "$fpath" "$dir"
fi
done < filenames.txt
正在运行的脚本:
$ ls -l dir\ with\ spaces/
total 0
$ cat filenames.txt
input.txt
ascii.txt
disksinfo.txt
process info.txt
$ ./makelinks.sh
$ ls -l dir\ with\ spaces/
total 0
lrwxrwxrwx 1 xieerqi xieerqi 9 1月 15 00:47 ascii.txt -> ascii.txt
lrwxrwxrwx 1 xieerqi xieerqi 13 1月 15 00:47 disksinfo.txt -> disksinfo.txt
lrwxrwxrwx 1 xieerqi xieerqi 9 1月 15 00:47 input.txt -> input.txt
lrwxrwxrwx 1 xieerqi xieerqi 16 1月 15 00:47 process info.txt -> process info.txt
它的工作方式很简单:while IFS= read -r line; do . . . done < filenames.txt
逐行读取 filenames.txt,每行都进入line
变量。我们检查该行是否不为空以及文件是否存在(该脚本将从原始文件和文件列表所在的同一目录执行)。如果这两个条件都成立,我们就会建立链接。
请注意,您应该确保文件以换行符结尾 - 如果最后一行不以换行符结尾(确实发生),则将跳过最后一行,因此不会为该文件创建任何链接。
尽管并不理想且并非没有怪癖,但对于 awksystem()
不可用的情况(现在可能很少见),这是一个可行的解决方案。
参数
稍微简单一点的 shell 方法是通过xargs
and bash -c '' sh
,我们再次使用标志逐行读取 filenames.txt -L1
,并将每一行用作 bash 的参数。使用$@
我们获取整个命令行参数数组($1、$2、$3...等)并将其转换为字符串变量,然后将其传递给ln
.最后sh
是设置$0
变量。是否有必要这样做还有争议,但这不是本问题的主题,因此让我们跳过它:)
$ xargs -I {} -L1 bash -c 'file="$@";fpath=$(realpath -e "$file"); ln -s "$fpath" ./dir\ with\ spaces/"$file" ' sh < filenames.txt
$ ls dir\ with\ spaces/
ascii.txt@ disksinfo.txt@ input.txt@ process info.txt@
Python
$ ls dir\ with\ spaces/
$ python -c "import os,sys;fl=[f.strip() for f in sys.stdin];map(lambda x: os.symlink(os.path.realpath(x),'./dir with spaces/' + x),fl)" < filenames.txt
$ ls dir\ with\ spaces/
ascii.txt@ disksinfo.txt@ input.txt@ process info.txt@
其工作方式很简单 - 我们重定向filenames.txt
到 python stdin
,将每一行读入 list fl
,然后运行os.symlink()
list 中的每个项目。冗长的一句台词,但有效。
珀尔
一个较短的版本是通过 Perl 实现的:
$ perl -lane 'use Cwd "realpath";symlink(realpath($_),"./dir with spaces/" . $_)' < filenames.txt
$ ls dir\ with\ spaces/
ascii.txt@ disksinfo.txt@ input.txt@ process info.txt@
答案2
对于此类任务,请勿使用调用外部命令system()
或类似函数的脚本语言。它呈现出相同的危险和陷阱如 Bourne Shell 的eval
.
xargs
让您安全高效地完成此操作:
xargs -d '\n' ln -s -t ~/directory\ with\ spaces/subdirectory/ < filenames.txt
filenames.txt
它的作用是通过在您作为参数给出的命令末尾添加 的内容来构建一个命令。然后它执行它。对于上面的示例,它将构建的命令如下所示:
ln -s -t directory file1 file2 ... fileN
有趣的是,您不必引用 filenames.txt 中的任何内容,也不必对目录使用嵌套引用。
答案3
在字符串文字中,使用双反斜杠在传递给 shell 的字符串中获取单个反斜杠,这会导致 shell 按字面解释下一个字符:
awk 'system("… ~/directory\\ with\\ spaces/subdirectory/")'
要引用 shell 代码片段中使用的输入,一种方法是在每个字符之前添加反斜杠。可移植的 awk 并不能让这变得容易(GNU awk 有可以做到的扩展)。或者,在每个元素周围放置单引号,并将元素中的所有单引号替换为'\''
。 Awk 使这个解决方案变得简单:只需使用gsub
.如果您的 awk 代码片段位于 shell 脚本中,则可以使用反斜杠八进制转义符将单引号放入 awk 字符串文字中。
<filenames.txt awk '{
gsub(/\047/, /\047\\\047\047/, $0);
system("ln -s \047" $0 "\047 ~/directory\\ with\\ spaces/subdirectory/");
}'