4

Suppose I have a directory structure as follows

test
test/a
test/b

Now I want to execute a command, such that in the . folder I can execute a command on the basename of the files a and b.

So basically, I want something like this, which I naively tried

find test -type f -exec touch `basename {}` \;

Only, this does not result in empty files a and b in the parent directory. Suppose that my touch command can only take a single argument.

This will result in a directory structure like this

a
b
test
test/a
test/b

I know how to do this in a bash script, but I am interested in single command solution.

2
  • 1
    Please explain what you want to achieve, perhaps with a directory tree before and after. The phrase "Only, this does not result in empty files a and b in the parent directory." appears to suggest you want to create files while your opening statement suggests the files already exist.
    – Bram
    May 30, 2012 at 7:32
  • @Bram I had the impression it follow a bit from my example find command, but I added it to the question.
    – Bernhard
    May 30, 2012 at 7:35

5 Answers 5

5

Execute bash.

find ... -exec bash -c 'touch "${1##*/}"' btouch {} \;

Here ${1##*/} means: remove the longest match from the second argument ending with a /, thus keeping everything after the last /.

btouch can be any dummy name, to make it appear properly in the active processes list (ps)

8
3

Try this at your own risk:

find test -type f -exec echo touch \"\$\(basename "'{}'"\)\" \; | bash

Tested with filenames with spaces.

2

Try the execdir option for find: it executes the command you specify in the directory of the file, using only its basename as the argument

From what I gather, you want to create "a" and "b" in the "main" directory. We can do that by combining $PWD and the -execdir option. Have a look at the solution below. (The && find … ls parts are for output only, so you can see the effects. You'll want to use the command before the &&.)

First, I set up the testing environment:

-----[ 15:40:17 ] (!6293) [ :-) ] janmoesen@mail ~/stack 
$ mkdir test && touch test/{a,b} && find . -exec ls -dalF {} +
drwxr-xr-x 3 janmoesen janmoesen 4096 2012-05-31 15:40 ./
drwxr-xr-x 2 janmoesen janmoesen 4096 2012-05-31 15:40 ./test/
-rw-r--r-- 1 janmoesen janmoesen    0 2012-05-31 15:40 ./test/a
-rw-r--r-- 1 janmoesen janmoesen    0 2012-05-31 15:40 ./test/b

This is what happens when you use a simple -exec — the original files are touched:

-----[ 15:40:30 ] (!6294) [ :-) ] janmoesen@mail ~/stack 
$ find test -type f -exec touch {} \; && find . -exec ls -dalF {} +
drwxr-xr-x 3 janmoesen janmoesen 4096 2012-05-31 15:40 ./
drwxr-xr-x 2 janmoesen janmoesen 4096 2012-05-31 15:40 ./test/
-rw-r--r-- 1 janmoesen janmoesen    0 2012-05-31 15:40 ./test/a
-rw-r--r-- 1 janmoesen janmoesen    0 2012-05-31 15:40 ./test/b

However, if we combine $PWD with the argument placeholder {} and use -execdir, we achieve what (I think) you want:

-----[ 15:40:57 ] (!6295) [ :-) ] janmoesen@mail ~/stack 
$ find test -type f -execdir touch "$PWD/{}" \; && find . -exec ls -dalF {} +
drwxr-xr-x 3 janmoesen janmoesen 4096 2012-05-31 15:41 ./
-rw-r--r-- 1 janmoesen janmoesen    0 2012-05-31 15:41 ./a
-rw-r--r-- 1 janmoesen janmoesen    0 2012-05-31 15:41 ./b
drwxr-xr-x 2 janmoesen janmoesen 4096 2012-05-31 15:40 ./test/
-rw-r--r-- 1 janmoesen janmoesen    0 2012-05-31 15:40 ./test/a
-rw-r--r-- 1 janmoesen janmoesen    0 2012-05-31 15:40 ./test/b
1

How about this?

$ touch $(find test -type f -exec basename {} \;)
2
  • Suppose that my touch command only takes one argument at a time?
    – Bernhard
    May 30, 2012 at 7:06
  • 3
    Will break on filenames with whitespace.
    – Chris Down
    May 30, 2012 at 9:11
1

Invoke a shell to do a bit of string processing on the path. You can use the ${VARIABLE##PATTERN} string manipulation construct to strip off a prefix matching PATTERN from VARIABLE — the pattern */ strips off the leading directory names.

find test -type f -exec sh -c 'for x; do touch "${x##*/}"; done' _ {} +

An alternate method with GNU find is to have it change into the subdirectories. Invoke a shell to change back to the original directory.

find test -type f -execdir sh -c 'cd "$0" && touch -- "$@"' "$PWD" {} +

Or drop find and use zsh. Specifically, add the t history modifier as a glob qualifier.

touch test/**/*(:t)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .