Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

On a Linux Centos 4 machine, I am trying to create a simple bash command line to walk a directory structure below an arbitrary current directory and in each subdirectory touch a file, list the directory contents but pipe them to /dev/null, and remove the touched file. The obscure point of this script is to tickle the underlying NFS client/server system to ensure the contents of each directory are reflecting a change made on a different machine which otherwise may take some time to propogate. I have found this workaround avoids the delay. Ignoring the merits of my reason for doing this, why doesn't my proposed bash script work?

[CentosMachine] find . -type d -print0 | xargs -0 -I {} pushd {}; touch xYzZy.fixZ; ls &> /dev/null; rm -f xYzZy.fixZ; popd
xargs: pushd: No such file or directory
bash: popd: directory stack empty

The find command is presently returning:

.
./dir
./emptyDir
./dirOfDir
./dirOfDir/ofDir
./dirOfDir/ofDir/Dir(empty)

At first I thought perhaps the ( and ) in one of the directory names might be the issue, but renaming that directory to be ./dirOfDir/ofDir/Dir_empty_ did not change the symptom. I also tried looking at strace output but did not see anything that helped, but did see the directories being processed.

Here is a snippet of the end of the strace output with that directory renamed to use underscores instead of parentheses:

[...]
chdir("ofDir")                          = 0
lstat64(".", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
lstat64("Dir_empty_", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
open("Dir_empty_", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY) = 4
fstat64(4, {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
fcntl64(4, F_SETFD, FD_CLOEXEC)         = 0
getdents64(4, /* 2 entries */, 32768)   = 48
getdents64(4, /* 0 entries */, 32768)   = 0
close(4)                                = 0
chdir("Dir_empty_")                     = 0
lstat64(".", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
chdir("..")                             = 0
lstat64(".", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
chdir("..")                             = 0
lstat64(".", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
chdir("..")                             = 0
lstat64(".", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
fchdir(3)                               = 0
write(1, ".\0./dir\0./emptyDir\0./dirOfDir\0./"..., 75) = 75
exit_group(0)                      = ?
share|improve this question

2 Answers 2

up vote 1 down vote accepted

xargs is a very useful tool for

  1. executing commands with arguments taken from a dynamic source, and
  2. minimizing the number of command invocations by building long command lines, with multiple arguments.

When you’re not doing #2 (i.e., executing one command per argument, as you are doing), xargs isn’t quite so valuable; there are other ways to get the commands executed.  In particular, if the source of the arguments is find, you can use the -exec option:

find . -type d -exec bash -c 'pushd "{}" &> /dev/null; touch xYzZy.fixZ; ls &> /dev/null; rm -f xYzZy.fixZ; popd &> /dev/null'

But this answer, like yours, invokes a shell process for each directory.  And each process has its own execution environment; changing the working directory in a sub-process has no effect on the parent process.  So you don’t need the pushd and popd.  And you don’t need to specify bash; plain old sh will do.  And, if you aren’t setting modification times, you don’t need touch; a redirected null command will create a file.  So we can reduce the above to:

find . -type d -exec sh -c 'cd "{}"; > xYzZy.fixZ; ls &> /dev/null; rm -f xYzZy.fixZ'

For your use case, this might not matter, but in other situations you might want to perform the last three steps only if the cd succeeds:

find . -type d -exec sh -c 'cd "{}" && { > xYzZy.fixZ; ls &> /dev/null; rm -f xYzZy.fixZ;}'

Note that you need to have whitespace after the { and a semicolon before the }.

share|improve this answer

I found my answer with this stack overflow question. Put the multiple commands into a form like this:

bash -c 'command1; command2; ...'

Which applied here gives:

find . -type d -print0 | xargs -0 -I {} bash -c 'pushd "{}"; touch xYzZy.fixZ; ls &> /dev/null; rm -f xYzZy.fixZ; popd'

Note the addition of double quotes around the pushd "{}" so that the directory with ( and ) works properly. Without that you get an error:

bash: -c: line 0: syntax error near unexpected token `('
bash: -c: line 0: `pushd ./dirOfDir/ofDir/Dir(empty) &> /dev/null; touch xYzZy.fixZ; ls &> /dev/null; rm -f xYzZy.fixZ; popd &> /dev/null'

However, the pushed and popd also need suppression to avoid output:

find . -type d -print0 | xargs -0 -I {} bash -c 'pushd "{}" &> /dev/null; touch xYzZy.fixZ; ls &> /dev/null; rm -f xYzZy.fixZ; popd &> /dev/null'
share|improve this answer
    
You answered your question without answering your question. The short answer is that xargs can’t take a compound command; you need to wrap the compound command in a string and pass it to the shell. –  Scott Jul 18 at 22:18

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.