web-dev-qa-db-ja.com

ディレクトリの下のフルパスとファイル名を見つけて、実行可能ファイルに引数として渡します

ディレクトリの下にあるすべての.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"を2つの引数として正しく分割できないため、問題が発生しました。


例:

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
    

    ご覧のとおり、xargsは2つのパスを/mnt/tokenized/test.txt/mnt/test/test33.txtで混合しているため、エラーNo such file or directoryが発生します。

それを機能させる方法は?

5
Mithril
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がnull区切りリストを解体するときが来たときに、正しい方法で解体できるように、フルパス名もnull区切り文字とともに渡す必要があります。

そうしないと、1つのファイルの完全パス名が次のファイルのベース名にマージされ、複数のファイル名の場合に観察された現象が発生します。

次に、一度に2つの引数をbash alligatorにフィードする必要があります。そうしないと、許可されている数だけ消費されますが、実行可能ファイル./thulacには最初の2つだけが渡されます。

より良い代替策は、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'
2
user218374

xargsは、xargsが不適切に処理されることで悪名高いため、常に '-'で始まり、ダブルスペース、 'および "を含む入力でソリューションをテストする必要があります。

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

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

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

<と>は、parallelで始まるシェルによって解釈されるため、引用符で囲む必要があります。代わりに、parallelで始まるシェルによって解釈されるようにします。

2
Ole Tange

問題があるのはあなたのfindコマンドです。
2つの名前を区切るには、printf形式でスペースを含めます

find /mnt/test -name "*.txt"  -print0 -printf " %f\n"
                                               ^ ( note the space above)
1
amisax

スクリプトが何を達成する必要があるかを正確に伝えることはできませんが、各奇数ファイルを最初の引数として渡し、各偶数ファイル名を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 and = GNU xargs:

find /tmp/test -name "*.txt" -exec sh -c '
    ./thulac < "$1" > "/mnt/tokenized/$(basename "$1")"' sh {} \;

ご了承ください {}は、fishシェルを使用する場合にのみ引用する必要がありますが、これは非常にまれな状況です。

1
jlliagre