Lorccan
Lorccan

Reputation: 823

find piped to xargs with complex command

I am trying to process DVD files that are in many different locations on a disk. The thing they have in common is that they (each set of input files) are in a directory named VIDEO_TS. The output in each case will be a single file named for the parent of this directory.

I know I can get a fully qualified path to each directory with:

find /Volumes/VolumeName -type d -name "VIDEO_TS" -print0

and I can get the parent directory by piping to xargs:

find /Volumes/VolumeName -type d -name "VIDEO_TS" -print0 | xargs -0 -I{} dirname {}

and I also know that I can get the parent directory name on its own by appending:

| xargs -o I{} basename {}

What I can't figure out is how do I then pass these parameters to, e.g. HandBrakeCLI:

./HandBrakeCLI -i /path/to/filename/VIDEO_TS -o /path/to/convertedfiles/filename.m4v

I have read here about expansion capability of the shell and suspect that's going to help here (not using dirname or basename for a start), but the more I read the more confused I am getting!

Upvotes: 3

Views: 878

Answers (2)

Charles Duffy
Charles Duffy

Reputation: 296019

You don't actually need xargs for this at all: You can read a NUL-delimited stream into a shell loop, and run the commands you want directly from there.

#!/bin/bash

source_dir=/Volumes/VolumeName
dest_dir=/Volumes/OtherName

while IFS= read -r -d '' dir; do
  name=${dir%/VIDEO_TS} # trim /VIDEO_TS off the end of dir, assign to name
  name=${name##*/}      # remove everything before last remaining / from name
  ./HandBrakeCLI -i "$dir" -o "$dest_dir/$name.m4v"
done < <(find "$source_dir" -type d -name "VIDEO_TS" -print0)

See the article Using Find on Greg's wiki, or BashFAQ #001 for general information on processing input streams in bash, or BashFAQ #24 to understand the value of using process substitution (the <(...) construct here) rather than piping from find into the loop.


Also, find contains an -exec action which can be used as follows:

source_dir=/Volumes/VolumeName
dest_dir=/Volumes/OtherName

export dest_dir # export allows use by subprocesses!

find "$source_dir" -type d -name "VIDEO_TS" -exec bash -c '
  for dir; do
    name=${dir%/VIDEO_TS}
    name=${name##*/}
    ./HandBrakeCLI -i "$dir" -o "$dest_dir/$name.m4v"
  done
' _ {} +

This passes the found directory names directly on the argument list to the shell invoked with bash -c. Since the default object for for loop to iterate over is "$@", the argument list, this implicitly iterates over directories found by find.

Upvotes: 4

rici
rici

Reputation: 241961

If I understand what you are trying to do, the simplest solution would be to create a little wrapper which takes a path and invokes your CLI:

File: CLIWrapper

#!/bin/bash
for dir in "$@"; do
  ./HandBrakeCLI -i "${dir%/*}" -o "/path/to/convertedfiles/${dir##*/}.m4v"
done

Edit: I think I misunderstood the question. It's possible that the above script should read:

./HandBrakeCLI -i "$dir" -o "/path/to/convertedfiles/${dir##*/}.m4v"

or perhaps something slightly different. But the theory is valid. :)


Then you can invoke that script using the -exec option to find. The script loops over its arguments, making it possible for find to send multiple arguments to a single invocation using the + terminator:

find /Volumes/VolumeName -type d -name "VIDEO_TS" -exec ./CLIWrapper {} +

Upvotes: 2

Related Questions