3

I accidentally pasted into shell and created a bunch of empty files that are all named random numbers. What is an effective way to remove all of these at once?

There are other files in the directory that I need; they contain numbers in them but any file in there that starts with a number is bad. Can you like regex delete?

0

4 Answers 4

7

You are saying that the files you want to delete are empty. A way to delete those files would be to delete only empty files. In that way, you will not delete a file with any content. I believe this is safer than classifying them by name. A command to delete empty files from the current directory would be:

find ./ -size 0 -delete

Stéphane Chazelas contributed constructive comment. A better command would be:

find ./ -maxdepth 1 -type f -size 0 -delete
3
  • 3
    That also removes empty files (of any type, including directories as long as they're empty) in subdirectories recursively. Note that -delete is a non-standard extension (from BSD, also found in GNU's). Commented Feb 28, 2022 at 6:58
  • Thanks for the comment, but I tried to make an empty directory and it was not found by the find command.
    – nobody
    Commented Feb 28, 2022 at 7:47
  • 2
    that directory would also need to be of size 0. On some filesystems like ext4, it's not possible to have directories of size 0. Commented Feb 28, 2022 at 7:54
5

With zsh:

rm -f -- *(.L0)

Would remove the regular (.) non-hidden empty files (of Length 0).

In bash or other shells:

zsh -c 'rm -f -- *(.L0)'

To remove files¹ whose name is made of only ASCII decimal digits:

rm -f <->

Where <-> is <x-y> to match numbers in a range but with no bound. Again, with other shells, you can use zsh -c '...'.

In bash, you could also do:

(shopt -s extglob failglob; rm -f +([0123456789])

Or in ksh93:

rm -f ~(N)+([0123456789])

You can combine the two (remove all-numeric empty regular files) with:

rm -f <->(.L0)

With the POSIX shell and utilities, the equivalent would be:

LC_ALL=C find . ! -name . -prune -type f -size 0c \
  ! -name '*[!0-9]*' -exec rm -f {} +

To find all-numeric files, we find all files except those that contain at least one non-digit. -size 0c matches files whose size in bytes² is 0.

LC_ALL=C makes sure the 0-9 range only includes 0123456789, but also and maybe more importantly for that command to work properly if there are filenames encoded in a charset different from that of the locale. For instance, without it, with GNU find and in a UTF-8 locale, a file called $'St\xe9phane' (Stéphane encoded in latin1) would be deleted, not because non-digits can't be found in it (it's only made of non-digits), but because it contains that 0xe9 byte that can't be decoded into a character, so * (which matches 0 or more characters) would fail to match.

zsh globs don't have the problem, as bytes not forming part of characters are treated as some form of special characters, while with bash globs and with current versions, bash switches to byte-wise matching when input strings can't be decoded into characters (making it behave as if in the C locale).


¹ this time not limited to regular files, also including symlinks, fifos... Files of type directory would not be deleted however as rm won't delete directories unless you pass the -r option.

² You could also use -size 0 (whose size in 512-byte units is 0), which would also work here as sizes are rounded up so a file of size one byte would be considered to be made of one 512-byte unit as far as -size 0 is concerned, but more generally, for exact size match, I would recommend using that c suffix as -size 1 for instance is not for files of size 512, but for files of size 1 to 512. Use -size 512c for files whose size is exactly 512. For zsh's L glob qualifier, the default unit is byte, not 512-byte unit.

1
3

You can use a glob. e.g.

echo rm [0-9]*

Remove the echo once you've verified that it won't delete anything you want to keep. BTW, if you have rm aliased to rm -i (which is a pretty common safeguard against accidentally deleting files), you'll want to override that by running command rm [0-9]* instead of just rm [0-9]*, otherwise you'll be typing y for each file.

Alternatively, mv the files into a newly-created directory, individually move any you want to keep back to where they came from, and delete the directory. e.g.

mkdir /tmp/junk
mv [0-9]* /tmp/junk
cd /tmp/junk
# investigate and mv any you want to keep
cd -
rm -rf /tmp/junk

Other alternatives:

If you've got more files to delete than will fit on a single bash command line:

for i in {0..9}; do rm "$i"*; done

or

find . -maxdepth 1 -type f -empty -name '[0-9]*' -delete

or dry-run version:

find . -maxdepth 1 -type f -empty -name '[0-9]*' -print | less

The -maxdepth 1 option prevents find from recursing into sub-directories, and -type f restricts it to matching only regular files (not directories, sockets, named pipes, etc), and -empty ensures it only matches empty files.

Or find and mv:

mkdir /tmp/junk
find . -maxdepth 1 -type f -empty -name '[0-9]*' -exec mv -t /tmp/junk/ {} +

(requires GNU mv for the -t option to specify the target directory before the list of files to move. GNU mv is standard on Linux)

4
  • Hah! Yes, a much simpler solution than mine. I get too used to using the for loop in bash :)
    – mdmay74
    Commented Feb 28, 2022 at 4:03
  • If you've got many thousands of files (i.e. more than can fit in one command line - about 2 MB on linux), you might have to use a loop. Or use multiple rm commands to delete 0*, then 1*, 2*, 3*, etc (which could easily be done with a for loop - for i in {0..9}; do rm "$i"*; done). Or use find . -maxdepth 1 -type f -name '[0-9]*' -delete (use -print instead of -delete for a dry-run test, and pipe find's output into less or something)
    – cas
    Commented Feb 28, 2022 at 4:12
  • Note that rm [0-9]* removes files whose name starts with a digit (well, in bash, that's all characters that sort between 0 and 9 which is not limited to digits), not only the ones that are entirely made of digits. Commented Feb 28, 2022 at 6:49
  • that's what the OP asked for "...any file in there that starts with a number is bad"
    – cas
    Commented Feb 28, 2022 at 15:23
0

If you're certain that all files starting with a number are to be deleted, then one way to do it would be to use the bash for loop:

for n in [0-9]* ; do rm -i "${n}" ; done

We loop through all files that fit the shell glob [0-9]* (which means a digit followed by one or more of any character). For each of those files (temporarily called $n, or for safety "${n}") we remove them - although I've added the -i for interactive, as I'm a little wary of removing a whole load of files automatically. You may remove this at your discretion.

For a bit more safety, you could check each file to see if it is empty before removing:

for n in [0-9]* ; do if [[ `ls -l "${n}" |sed 's/[^ ]* [^ ]* [^ ]* [^ ]* \([0-9]*\) .*/\1/'` -eq 0 ]] ; then rm "${n}" ; fi ; done

So now for each file, we pipe a long listing through a sed substitution to replace the line with the fifth field, which is the filesize, and test if it is equal to 0. If so, then it should be safe to remove without needing to say yes after each file.

This worked for me, but if you're removing lots of files around other files that you want to keep, it might be worthwhile starting with the -i to check that it is selecting the correct files. Then, when you're comfortable, you could Ctrl-C and run it again without the -i.

Or backup the directory, run the command on one copy, and see if the diff -r looks okay before removing your backup.

4
  • 1
    The first example will run rm once for each matching file - very slow. The second example runs ls, sed, and rm for each matching file - extremely slow. Worse, it parses the output of ls - see Why not parse ls (and what to do instead)? for why that is a terrible idea. If you really must use a loop and run rm individually for each filename, use (GNU) stat -c '%s' "$n" instead, no need for ls or sed. Or, better yet, use find's -empty predicate.
    – cas
    Commented Feb 28, 2022 at 4:34
  • @cas Thanks for the feedback. I take your point that my solution would be unwieldy (putting it mildly) for large sets of files. In fact, although when I was writing it (and spending time checking that it worked and was safe) I thought it was a cool and groovy answer, I see now that it is not very useful and suggests some bad habits that I ought to break out of. I think I should delete my answer. Is that the right thing to do, or do you think someone might learn from the comments pointing out its flaws?
    – mdmay74
    Commented Feb 28, 2022 at 5:48
  • There’s no need for either ls or stat to check whether a file is empty — test’s -e and -s checks can be combined to the same effect. Commented Feb 28, 2022 at 6:48
  • That's true, I should have mentioned that too but I was focusing on showing a good, generic alternative to parsing ls - and stat -c or stat --printf can give you a lot more than just file size - permissions, owner, group, timestamps, and more, most of which you can also use with specific find predicates or output with find ... -printf
    – cas
    Commented Feb 28, 2022 at 15:28

You must log in to answer this question.

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