Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

share|improve this question
    
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 '12 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 '12 at 7:35

5 Answers 5

up vote 0 down vote accepted

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
share|improve this answer

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)

share|improve this answer
    
This one does not work for me. touch: missing file operand. Also, what is 1##* and btouch? –  Bernhard May 30 '12 at 7:03
    
Fixed. Parameter expansion. $0. –  Ignacio Vazquez-Abrams May 30 '12 at 7:03
    
This works indeed, can you explain a bit why? Especially this 1##* Is this to strip the path? –  Bernhard May 30 '12 at 7:09
1  
1  
Because it looks silly. Plus, $0 shows up in ps and the like, so that you can identify the child process. –  Ignacio Vazquez-Abrams May 30 '12 at 7:51

Try this at your own risk:

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

Tested with filenames with spaces.

share|improve this answer

How about this?

$ touch $(find test -type f -exec basename {} \;)
share|improve this answer
    
Suppose that my touch command only takes one argument at a time? –  Bernhard May 30 '12 at 7:06
3  
Will break on filenames with whitespace. –  Chris Down May 30 '12 at 9:11

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)
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.