1

I have written a shell script to touch all the files in the /tmp directory. However, there was an error message saying that /bin/touch: Argument list too long. What's the cause of the problem?

#!/bin/bash

dayNo=1
while test $dayNo -le 200
do
    touch `find /tmp/`
    sleep 86000
done
  • 1
    In your example, you forget to increment dayNo. – Dubu Aug 20 '14 at 6:27
  • 1
    Note to all: Don't forget the -c option to touch, this should avoid a race condition if the file is removed prior to touching it, preventing touch from re-creating it. – MattBianco Aug 20 '14 at 6:54
4

You have so many files in /tmp that you can't fit all the names on the command line at once (the version you have is also unsafe if any paths have whitespace in them).

The good news is that find can do this for you safely and correctly:

find /tmp -exec touch -c '{}' +

will find all the files as before, and then run touch as many times as necessary with as many files as will fit each time.

find's -exec option takes a command to run for matched files and substitutes the paths where {} is. + means it runs the command with many files at once, or \; would run touch once for each file.


As noted in the comments, -execdir touch -c '{}' \; is strictly safer here, although it will likely be quite a bit slower, and there's not much benefit to be had on a single-user system. It avoids certain race conditions by switching into each subdirectory first, and then running the command only for files in that directory each time. The -execdir option is a non-standard extension, but it is supported in GNU, FreeBSD, OpenBSD, NetBSD, and OS X finds; the other commercial Unices generally don't have it. I'm not sure what you're using, but take the above into account if it's applicable.

  • In a world writeable dir like /tmp, find /tmp -execdir touch -c {} \; is safer (with find implementations that support it) (though a lot slower). – Stéphane Chazelas Aug 20 '14 at 7:01
  • Actually, while -execdir helps for rm, it doesn't help much for touch as touch will follow symlinks as well. Some have a -h. Best is to add a ! -type l otherwise. – Stéphane Chazelas Aug 20 '14 at 7:25
  • In theory, find /tmp -execdir touch -c {} + would be almost as fast as -exec unless there are lots of directories with few files. However GNU find has a long-standing bug that makes -execdir … + consume one file at a time rather than as many files in the same directory that would fit? – Gilles Aug 20 '14 at 20:52
1

The shell substitutes the result of the expression in the backticks into the line. But the system has limits on how long an argument list can be for a command. So if you have a few thousand files in /tmp, it's trying to create a command with thousands of names as an argument. This fails.

There are several ways to do this more easily. Find itself can run the command. Most modern versions will allow you to split it and run several individual touch commands, each under the limit.

find /tmp -exec touch {} +
0

The main problem with the backtick invocation in the question is that first the find command lists all files under /tmp, and then passes that (huge) list as arguments to touch, which is too much for a single invocation. There is also a problem if filenames contain spaces, newlines, semicolons, ampersands, pipe chars or similar.

Better to use this construct:

find /tmp -type f -exec touch -c '{}' +

The find command recursively lists all files (directories, symlinks, device specials are also "files" in unix) under /tmp.

-type f tells find to only select regular files

The -c option to touch prevents touch from creating files that do not exist.

It seems you omitted some logic in your example, but if you want to do it only 200 times, you could try the following:

#!/bin/bash

dayNo=1
while test $dayNo -le 200
do
    find /tmp -type f -exec touch -c '{}' +
    sleep 86000
    dayNo=$(expr 1 + ${dayNo})
done
  • 1
    -exec {} + is standard and portable. For once, it was the GNU implementation that didn't support it until recently. Using -print | xargs is almost as bad as using command substitution. Would touch /etc/shadow if someone creates a file called "/tmp/ /etc/shadow" as well. – Stéphane Chazelas Aug 20 '14 at 7:04
  • @StéphaneChazelas, you are of course correct (as always). Thanks! – MattBianco Aug 20 '14 at 7:07
  • 1
    It's not too much for the shell, it's too much for the system (for the execve() system call). – Stéphane Chazelas Aug 20 '14 at 7:26
  • @StéphaneChazelas it's a pleasure and very educational reading your comments. Can you also say if it is the number of arguments or just the size (as in number of characters) that is the limit for the execve() system call, or are there several limits, one for argument count and one for argument or environment size that causes the E2BIG error? – MattBianco Aug 20 '14 at 7:35
  • if find supports -print0 and xargs supports -0 or --null: find /tmp -type f -print0 | xargs -0 touch -c could be an alternative to the + notation in find -exec – MattBianco Aug 20 '14 at 7:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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