我想找到.txt
目录下所有文件的完整路径和文件名,并传递给可执行文件./thulac
。
我花了一些时间才达到:
find /mnt/test -name "*.txt" -print0 |xargs -l bash -c './thulac < $0'
但这只能找到完整路径。
从具有多个参数的 xargs , 我懂了:
echo argument1 argument2 argument3 | \
xargs -l bash -c 'echo this is first:$0 second:$1 third:$2' | xargs
我想要实现的目标是:
find /mnt/test -name "*.txt" -print0 -printf "%f" | \
xargs -0 bash -c './thulac < $0 > $1'
虽然在这里,当有多个文件时xargs
无法正确拆分为两个参数,这让我很困惑。-print0 -printf "%f"
例子:
find /mnt/test -name "*.txt" -print0 -printf "%f" | \
xargs -0 -I bash -c './thulac < $0 > /mnt/tokenized/$1'
如果
/mnt/test
只有一个文件,上述命令就可以工作。但如果
/mnt/test
有多个文件(无论何种语言):[root@localhost THULAC]# ls /mnt/test test33.txt test.txt [root@localhost THULAC]# find /mnt/test -name "*.txt" -print0 -printf "%f" | \ xargs -0 bash -c './thulac < $0 > /mnt/tokenized/$1' /mnt/test/test.txt: /mnt/tokenized/test.txt/mnt/test/test33.txt: No such file or directory
如您所见,
xargs
将两条路径混合在一起/mnt/tokenized/test.txt/mnt/test/test33.txt
,这会导致错误No such file or directory
。
如何让它发挥作用?
答案1
find /tmp/test -name '*.txt' \
-exec bash -c './thulac < "$(readlink -f {})" > "/mnt/tokenized/$(basename {})"' \;
使用 find 搜索文件并对结果执行命令。这样bash -c 'command'
你就可以执行多个$()。
用于readlink -f {}
创建结果的完整路径。
用于basename {}
从结果中去除路径。
答案2
与您合作时,xargs
您应该始终使用以“-”开头并包含双空格、“和”的输入来测试您的解决方案,因为它xargs
因处理这些问题而臭名昭著:
mkdir -- '-" '"'"
seq 10 > ./-\"\ \ \'/'-" '"'".txt
这是使用 GNU Parallel 的解决方案:
find . -name "*.txt" -print0 |parallel -0 ./thulac '<' {} '>' {/}
< 和 > 需要加引号,否则它们将被启动的 shell 解释parallel
。我们希望它们由 启动的 shell 来解释parallel
。
答案3
find /mnt/test -name "*.txt" -print0 -printf "%f\0" |
xargs -0 -n 2 bash -c 'shift $1; ./thulac < $1 > /mnt/tokenized/$2' 2 1
您还希望传递带有空分隔符的完整路径名,以便当需要xargs
拆除空分隔列表时,它可以以正确的方式执行此操作。
否则,将会发生的情况是,一个文件的完整路径名将被合并到下一个文件的基本名中,这是您在多个文件名的情况下观察到的现象!
然后您需要一次向 提供 2 个参数bash alligator
,否则它将消耗尽可能多的参数,但它只将前两个参数传递给您的可执行文件./thulac
。
更好的选择是放弃xargs
& 在 中完成所有工作find
,因为 xargs 一次处理 2 个参数,这剥夺了xargs
.在此版本中,我们提供完整路径名bash
并自行计算文件名,bash
而不是依赖find
它来执行此操作。
find /mnt/test -name "*.txt" -exec bash -c './thulac < "$1" \
> "/mnt/tokenized/${1##*/}"' {} {} \;
问题的根源
1. Good case when only 1 file present
-print0 -printf '%f'
/mnt/test/test.txt\0test.txt
|-----------------|--------|
arg0 = /mnt/test/test.txt
arg1 = test.txt
bash -c 'thulac < $0 > /mnt/tokenized/$1'
thulac < /mnt/test/test.txt > /mnt/tokenized/test.txt
2. Error case when > 1 file present
-print0 -printf '%f'
/mnt/test/test.txt\0test.txt/mnt/test/test33.txt\0test33.txt
|-----------------|-----------------------------|----------|
arg0 = /mnt/test/test.txt
arg1 = test.txt/mnt/test/test33.txt
arg2 = test33.txt
bash -c 'thulac < $0 > /mnt/tokenized/$1'
thulac < /mnt/test/test.txt > /mnt/tokenized/test.txt/mnt/test/test33.txt
使固定
We saw that the mixup occurred due to the absence of the delimiter '\0' in the -printf "%f"
So the correct way is:
find ... -print0 -printf "%f\0" | xargs ...
Ensuring that the list is partitioned at the right places and the
sequence of fullpath1+file1\0fullpath2+file2\0... is maintained.
Now coming to the 'xargs' part, we write:
xargs -0 -n 2 bash -c '...' 2 1
Points to observe are the following:
a) '-0' => arguments to xargs will be taken to be NULL separated.
b) -n 2 => we feed 2 args at a time to bash from the total pool
delivered to xargs by find.
c) 2 1 is just a best practice to get over different shell's behavior
regarding what construes as $0, $1, $2, ...; In your particular case since you
already know that $0 -> first arg, $1 -> 2nd arg, we could just as well have
written what you did:
find ... | xargs -0 -n 2 bash -c './thulac < $0 > /mnt/tokenized/$1'
答案4
您没有准确地告诉您的脚本需要实现什么,但假设您希望将每个奇数文件作为第一个参数传递,每个偶数文件名作为第二个参数传递,以下是如何以可移植的方式执行此操作:
t=$(mktemp)
find /tmp/test -name "*.txt" -exec sh -c '
if [ -s $1 ]
then
./thulac < "$(<$1)" > "/mnt/tokenized/$2"
else
printf "%s" "$2" > "$1"
fi' sh $t {} \;
rm $t
如果您只想传递找到的每个文件的路径和文件名,答案更简单,仍然只使用可移植命令和语法(POSIX),即不依赖于 bash、GNU find 和 GNU xargs:
find /tmp/test -name "*.txt" -exec sh -c '
./thulac < "$1" > "/mnt/tokenized/$(basename "$1")"' sh {} \;
请注意,{}
仅在使用 shell 时才需要引用fish
,这是极不可能的情况。