問題の源

問題の源

.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複数のファイルがある場合、2つのパラメータに正しく分割されていないため、混乱しています。-print0 -printf "%f"


例:

find /mnt/test -name "*.txt" -print0 -printf "%f" | \
   xargs -0 -I bash -c './thulac < $0 > /mnt/tokenized/$1'
  1. /mnt/test上記のコマンドは、ファイルが1つしかない場合でも機能します。

  2. ただし、/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
    

    ご覧のとおり、2つのパス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常に「-」で始まり、二重スペース「and」を含む入力でソリューションをテストする必要があります。これはxargs、これらの問題に対処するために悪名高いからです。

mkdir -- '-"  '"'"
seq 10 > ./-\"\ \ \'/'-"  '"'".txt

GNU Parallelを使用するソリューションは次のとおりです。

find . -name "*.txt" -print0 |parallel  -0 ./thulac '<' {} '>' {/}

<と>は引用符で囲む必要があります。それ以外の場合は、起動シェルで解釈されますparallel。私たちはそれらを開始シェルによって解釈したいと思います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 正しい方法でこれを実行できるようにします。

それ以外の場合は、1つのファイルのフルパス名が次のファイルのデフォルト名にマージされ、これは複数のファイル名で観察される現象です。

その後、一度に2つのパラメータを指定する必要があります。bash alligatorそれ以外の場合は、できるだけ多くのパラメータを使用しますが、実行可能ファイルには最初の2つのパラメータのみが渡されます./thulac

より良いオプションは、xargsが一度に2つの引数を処理するため、xargsxargsですべての操作を実行することです。このバージョンではこれを行うのではなく、フルパス名を提供し、ファイル名を直接計算します。findxargsbashbashfind

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

スクリプトがどのように実装するべきかを正確に言うわけではありませんが、すべての奇数ファイルを最初の引数として渡し、すべての偶数ファイル名を2番目の引数として渡すと仮定すると、移植可能な方法でこれを行う方法は次のとおりです。

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 {} \;

引用は{}シェルを使用する場合にのみ必要です。fishこれはごくまれなシナリオです。

関連情報