If I had a folder of files, what script could I write to remove the files whose names don't have certain phrases?
My folder contains
oneApple.zip
twoApples.zip
threeApples.zip
fourApples.zip
I would want to remove the files whose names don't contain "one" or "three" anywhere within their filename.
After executing the script, the folder would only contain:
oneApple.zip
threeApples.zip
Using bash
With a modern bash with extglob enabled, we can delete files whose names do not contain one or three with:
rm !(*one*|*three*)
To experiment with how extglobs work, just use echo:
$ echo !(*one*|*three*)
fourApples.zip twoApples.zip
If the above doesn't work properly, then either your bash is out of date or extglob is turned off. To turn it on:
shopt -s extglob
Using find
find . -maxdepth 1 -type f ! -name '*one*' ! -name '*three*' -delete
Before running that command, you probably want to test it. Just remove the -delete and it will show you the files that it found:
$ find . -maxdepth 1 -type f ! -name '*one*' ! -name '*three*'
./twoApples.zip
./fourApples.zip
How it works:
.
This tells find to look in the current directory.
-maxdepth 1
This tells find not to recurse into subdirectories
-type f
This tells find that we only want regular files.
! -name '*one*'
This tells find to exclude files with one in their name.
! -name '*three*'
This tells find to exclude files with three in their name.
-delete
This tells find to delete the files that it found.
Related
I'm trying to remove all of the folder meta files from a unity project in the git repo my team is using. Other members don't delete the meta file associated to the folder they deleted/emptied and it's propagating to everyone else. It's a minor annoyance that shouldn't need to be seen so I've added this to the .gitignore:
*.meta
!*.*.meta
and now need to remove only the folder metas. I'd rather remove the metas now than wait for them to appear and have git remove them later. I'm using git bash on Windows and have tried the following commands to find just the folder metas:
find . -name '*.meta' > test.txt #returns folders and files
find . -regex '.*\.meta' > test.txt #again folders and files
find . -regex '\.[^\.]{0,}\.meta' > test.txt #nothing
find . -regex '\.[^.]{0,}\.meta' > test.txt #nothing
find . -regex '\.{2}' > test.txt #nothing
find . -regex '(\..*){2}' > test.txt #nothing
I know regex is interpreted differently per program/language but the following will produce the results I want in Notepad++ and I'm not sure how to translate it for git or git bash:
^.*/[^.]{0,}\.meta$
by capturing the lines (file paths from root of repo) that end with a /<foldername>.meta since I realized some folders contained a '.' in their name.
Once this is figured out I need to go line by line and git rm the files.
NOTE
I can also run:
^.*/.*?\..*?\.meta$\n
and replace with nothing to delete all of the file metas from the folders and files result, and use that result to get all of the folder metas, but I'd also like to know how to avoid needing Notepad++ as an extra step.
To confine the results only to indexed files use git ls-files, the swiss-army knife of index-aware file listing. git update-index is the core-command index munger,
git ls-files -i -x '*.meta' -x '!*.*.meta' | git update-index --force-remove --stdin
which will remove the files from your index but leave them in the work tree.
It's easier to express with two conditions just like in .gitignore. Match *.meta but exclude *.*.meta:
find . -name '*.meta' ! -name '*.*.meta'
Use -exec to run the command of your choice on the matched files. {} is a placeholder for the file names and ';' signifies the end of the -exec command (weird syntax but it's useful if you append other things after the -exec ... ';').
find . -name '*.meta' ! -name '*.*.meta' -exec git rm {} ';'
I'm trying to run through folders and subfolders (only, no files can be altered) in a given directory which have leading underscores and remove those leading underscores. I'm planning on accomplishing this with a simple shell script:
for folder in ./_* do
mv "$folder" "${folder:1}"
done
The above script doesn't work yet to specification for two reasons which I'm trying to correct here:
- one, the "./_*" does not work like it should, either throwing an error (./_*: No such file or directory) or selecting folders which do not have leading underscores too.
- two, it does not specify folders only...is there an option for the mv command which can do that?
Thanks
To find all folders starting with underscore use this find:
find . -type d -name '_*'
And to remove _ use:
find . -type d -name '_*' -exec bash -c 'f="$1"; mv "$f" "${f:1}" - {} \;
Using bash4 recursively :
shopt -s globstar
for dir in **/_*/; do
mv "$dir" "${dir:1}"
done
I misread the gzip documentation, and now I have to remove a ton of ".gz" files from many directories inside one another. I tried using 'find' to locate all .gz files. However, whenever there's a file with a space in the name, rm interprets that as another file. And whenever there's a dash, rm interprets that as a new flag. I decided to use 'sed' to replace the spaces with "\ " and the space-dashes with "\ -", and here's what I came up with.
find . -type f -name '*.gz' | sed -r 's/\ /\\ /g' | sed -r 's/\ -/ \\-/g'
When I run the find/sed query on a file that, for example, has a name of "Test - File - for - show.gz", I get the output
./Test\ \-\ File\ \-\ for\ \-\ show.gz
Which appears to be acceptable for rm, but when I run
rm $(find . -type f -name '*.gz'...)
I get
rm: cannot remove './Test\\': No such file or directory
rm: cannot remove '\\-\\': No such file or directory
rm: cannot remove 'File\\': No such file or directory
rm: cannot remove '\\-\\': No such file or directory
...
I haven't made extensive use of sed, so I have to assume I'm doing something wrong with the regular expressions. If you know what I'm doing wrong, or if you have a better solution, please tell me.
Adding backslashes before spaces protects the spaces against expansion in shell source code. But the output of a command in a command substitution does not undergo shell parsing, it only undergoes wildcard expansion and field splitting. Adding backslashes before spaces doesn't protect them against field splitting.
Adding backslashes before dashes is completely useless since it's rm that interprets dashes as special, and it doesn't interpret backslashes as special.
The output of find is ambiguous in general — file names can contain newlines, so you can't use a newline as a file name separator. Parsing the output of find is usually broken unless you're dealing with file names in a known, restricted character set, and it's often not the simplest method anyway.
find has a built-in way to execute external programs: the -exec action. There's no parsing going on, so this isn't subject to any problem with special characters in file names. (A path beginning with - could still be interpreted as an option, but all paths begin with . since that's the directory being traversed.)
find . -type f -name '*.gz' -exec rm {} +
Many find implementations (Linux, Cygwin, BSD) can delete files without invoking an external utility:
find . -type f -name '*.gz' -delete
See Why does my shell script choke on whitespace or other special characters? for more information on writing robust shell scripts.
There is no need to pipe to sed, etc. Instead, you can make use of the -exec flag on find, that allows you to execute a command on each one of the results of the command.
For example, for your case this would work:
find . -type f -name '*.gz' -exec rm {} \;
which is approximately the same as:
find . -type f -name '*.gz' -exec rm {} +
The last one does not open a subshell for each result, which makes it faster.
From man find:
-exec command ;
Execute command; true if 0 status is returned. All following
arguments to find are taken to be arguments to the command until an
argument consisting of ;' is encountered. The string{}' is
replaced by the current file name being processed everywhere it occurs
in the arguments to the command, not just in arguments where it is
alone, as in some versions of find. Both of these constructions
might need to be escaped (with a `\') or quoted to protect them from
expansion by the shell. See the EXAMPLES section for examples of the
use of the -exec option. The specified command is run once for
each matched file. The command is executed in the starting directory.
There are unavoidable security problems surrounding use of the -exec
action; you should use the -execdir option instead.
I am trying to strip all "?" in file names in a given directory who was got more subdirectories and they have subdirectories within it. I've tried using a simple perl regex script with system calls but it fails to recurse over each subdirectory, and going manually would be too much wasted time. How can I solve my problem?
You can use the find command to search the filenames with "?" and then use its exec argument to run a script which removes the "?" characters from the filename. Consider this script, which you could save to /usr/local/bin/rename.sh, for example (remember to give it +x permission):
#!/bin/sh
mv "$1" "$(echo $1| tr -d '?')"
Then this will do the job:
find -name "*\?*" -exec rename.sh {} \;
Try this :
find -name '*\?*' -exec prename 's/\?//g' {} +
See https://metacpan.org/module/RMBARKER/File-Rename-0.06/rename.PL (this is the default rename command on Ubuntu distros)
Find all the names with '?' and delete all of them. Probably -exec option could be used as well but would require additional script
for f in $(find $dir -name "*?*" -a -type f) ; do
mv $f ${f/?/}
done
I am trying to get all the zip files that are present under the directory named "target".
I want to search recursively under a given directory for this file pattern - "target/.zip"
This is the command that I have:
find . -type f \( -path '*/target/*' -a -name '*.zip' \)
This one gives me answers that don't strictly match my criteria.
This one also matches files like "target/foo/bar.zip"
I want to prune these results and only get the zip files that are present directly under the target directory. can someone help me with one?
How about this:
$ find . -type f ! -path '*/target/*/*.zip' -a -path '*/target/*.zip'