2

Editor's note: This question is ambiguous, because it conflates two unrelated tasks:
(a) to print mere filenames (without path components) using the -printf action, and
(b) to pass the mere filename as an argument in the context of an -exec action via {}
(a) is mistakenly perceived as a way to implement (b).
This confusion has led to at least one answer focusing on (a).

I'm trying to use the find command to list all directories in a certain path, but hide that path in the output. The -printf "%P\n" flag is supposed to hide /path/to/directory/, but it's not working:

find /path/to/directory/* -maxdepth 0 -type d -printf "%P\n" -exec sudo tar -zcpvf {}.tar.gz {} \;

For example, the above command would create archives with:

/path/to/directory/dir1
/path/to/directory/dir2
/path/to/directory/dir3

How can I modify my command to output this:

dir1
dir2
dir3

Please note: I know I can do the above by cd /path/to/directory/ then using the find command, but it's important that I avoid using cd and do it all with the single find command.

mklement0
  • 382,024
  • 64
  • 607
  • 775
GTS Joe
  • 3,612
  • 12
  • 52
  • 94

2 Answers2

2
find  /path/to/directory/* -maxdepth 0 -type d -exec basename {} \;

find all directories find /path/to/directory/* -maxdepth 0 -type d

-exec basename {} \; - execute basename command with result parameters from find

valch85
  • 99
  • 1
  • 8
  • While this code snippet may solve the question, [including an explanation](http://meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – gunr2171 Mar 19 '16 at 03:09
  • This suggestion doesn't solve anything at all, since it completely left out MY -exec flag option. – GTS Joe Mar 19 '16 at 03:12
  • This seems to work for a little while, then "Cannot stat: No such file or directory." Maybe xargs doesn't work with too many files? – GTS Joe Mar 19 '16 at 03:54
  • This answer correctly shows how to _print_ filenames only (which is _one_ of the things the OP asked for - though in a mistaken attempt to solve a different problem). However, an easier and more efficient solution with GNU `find` (as found on Linux) is `find /path/to/directory/* -maxdepth 0 -type d -printf '%f\n'`. Using `-exec` and assuming either GNU or BSD/OSX `basename`, a more efficient solution is `find /path/to/directory/* -maxdepth 0 -type d -exec basename -a {} +`. On the plus side, the command in this answer is POSIX-compliant. – mklement0 Mar 20 '16 at 16:29
0

Since you're only processing child directories (immediate subdirectories), a shell loop may be the simpler solution:

(cd "/path/to/dir" && for d in */; do sudo tar -zcpvf "${d%/}".tar.gz "$d"; done)

I know you want to avoid cd, but by enclosing the entire command in (…), it is run in a subshell, so the current shell's working dir. remains unchanged.

The remainder of this answer discusses how to solve the problem using GNU find.

The -execdir solution would work with BSD/OSX find too, and would actually be simpler there.


As for getting find's -printf to only print the filenames, without any directory components: use the %f format specifier:

find /path/to/dir/* -maxdepth 0 -type d -printf "%f\n"

This will print the mere names of all immediate subdirectories in the specified directory.

However, what you print has no effect on what {} expands to when using the -exec action: {} always expands to the path as matched, which invariably includes the path component specified as the input.

However, the -execdir action may give you what you want:

  • it changes to the directory at hand before executing the specified command

  • it expands {} to ./<filename> - i.e., the mere filename (directory name, in this case), prefixed with ./ - and passes that to the specified command.

Thus:

find /path/to/dir -mindepth 1 -maxdepth 1 -type d -execdir sudo tar -zcpvf {}.tar.gz {} \;

Caveat: -execdir only behaves as described for files that are descendants of the input paths; for the input paths themselves, curiously, {} still expands to the input paths as-is[1] .

Thus, the command above does not use globbing (/path/to/dir/*) with -maxdepth 0, but instead uses /path/to/dir and lets find do the enumeration of contained items, which are than at level 1 - hence -maxdepth 1; since the input path itself should then not be included, -mindepth 1 must be added.

Note that the behavior is then subtly different: find always includes hidden items in the enumeration, whereas the shell's globbing (*) by default does not.


If the ./ prefix in the {} expansions should be stripped, more work is needed:

find /path/to/dir -mindepth 1 -maxdepth 1 -type d \
  -execdir sh -c 'd=${1##*/}; sudo tar -zcpvf "$d".tar.gz "$d"' - {} \;

Involving the shell (sh) allows stripping the ./ prefix using a shell parameter expansion (${1##*/} would, in fact, strip any path component). Note the dummy argument -, which the shell assigns to $0, which we're not interested in here; {} then becomes shell parameter $1.


[1] With ./ prepended, if an input path is itself relative; note that BSD/OSX find does not exhibit this quirk: it always expands {} to the mere filename, without any path component.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • This ALMOST works! Except it puts an ugly blank directory in front, e.g.: ./dir/filename.txt. Is there any way to remove ./ so the output is just dir/filename.txt? – GTS Joe Mar 19 '16 at 04:29
  • Yes, tar works correctly, but instead of dir1 dir2 in my archive I'm getting ./dir1 ./dir2. – GTS Joe Mar 19 '16 at 04:42
  • find: paths must precede expression: -execdir Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression] – GTS Joe Mar 19 '16 at 05:12
  • @GTSJoe: Sounds like a typo; somewhere after `-exec` you have an _unquoted_ `/` - perhaps you forgot the single quotes around the shell command? – mklement0 Mar 19 '16 at 05:18