hasen
hasen

Reputation: 166282

Copy only executable files (cross platform)

I want to copy executable files from one directory to another.

The source directory includes all sorts of files I don't care about (build artifacts). I want to grab only the executable files, using a bash script that works on both OS X and Linux.

By executable, I mean a file that has the executable permission, and would pass test -x $filename.

I know I can write some python script but then I would be introducing a dependency on python (just to copy files!) which is something I really want to avoid.

Note: I've seem a couple of similar questions but the answers seem to only work on Linux (as the questions specifically asked about Linux). Please do not mark this as duplicate unless the duplicate question is indeed about cross platform copying of executable files only.

Upvotes: 2

Views: 3302

Answers (2)

mklement0
mklement0

Reputation: 439607

Your own answer is conceptually elegant, but slow, because it creates at least one child process for every input file (test), plus an additional one for each matching file (cp).

Here's a more efficient bash alternative that:

  • builds up an array of matching input files in shell code,
  • and then copies them using a single invocation of cp.
#!/usr/bin/env bash

exeFiles=()
for f in "$src_dir"/*; do [[ -x $f && -f $f ]] && exeFiles+=( "$f" ); done
cp "${exeFiles[@]}" "$dest_dir/"
  • exeFiles=() initializes the array in which to store the matching filenames.
  • for f in "$src_dir"/* loops over all files and directories located directly in $scr_dir; note how * must be unquoted for globbing (filename expansion) to occur.
    • [[ -x $f && -f $f ]] determines whether the item at hand is executable (-x) and a (regular) file -f; note that double-quoting variable references inside [[ ... ]] is (mostly) optional.
    • exeFiles+=( "$f" ) appends a new element to the array
  • "${exeFiles[@]}" refers to the resulting array as a whole and robustly expands to the array elements as individual arguments - see Bash arrays.

Upvotes: 3

hasen
hasen

Reputation: 166282

After some experimentation, this seems to work on both OS X and Ubuntu

find "$src_dir" -maxdepth 1 -type f -exec test -x {} \; -exec cp {} "$dest_dir/" \;

Note that the -maxdepth 1 is specific to my use case where I don't care about recursively going through all the directories.

-type f is necessary because directories also count as executables

I pass two -exec flags. The exec flag not only executes the command but also counts as a filter so that if the command returns a non-zero exit code, the file is filtered out.

The way to use exec is write out whatever command you want, use {} to supply the current file, and terminate with \;

The first -exec returns a success exit code only if the file is executable.

The second -exec performs the copy, but it's not executed if the first -exec fails.

Upvotes: 3

Related Questions