Remove files with the similar name except one? - linux

I want to remove all previous versions of Tableau Reader.app from applications apart from the latest one. I want to do this through command line. The problem that I am having is I want to remove all files containing tableau reader except for the file tableau reader 2019.2.
I have tried multiple ways but they aren't working. I am not very experienced with this. Any help is appreciated. The script below: Checks if the Application is running and if not goes about deleting the different versions(bit I need help on)
#!/bin/bash
shopt -s extglob
process="$4"
processrunning=$( ps axc | grep "${process}" )
if [ "$processrunning" != "" ] ; then
echo "$process IS running, do nothing"
echo "error: script failed"
exit 1
else
echo "$process is not running and will remove the old versions"
find $HOME/Applications/ -type f -not -name 'Tableau Reader 2019.2.app' | grep "tableau reader" | xargs rm
#rm -r $HOME/Applications/Tableau Reader?*.app![$HOME/Applications/"Tableau Reader 2019.2.app"]
#find. -type f !( -name 'Tableau Reader 2019.2.app') -exec rm -f "Tableau Reader"*.app {} +
fi

try
ls "tableau reader"* | sort | head -n -1 | xargs -d '\n' rm

You can try this:
find $HOME/Applications -type f -not -name 'Tableau Reader 2019.2.app' | grep "Tableau Reader" | xargs -I {} rm {}
The scripts finds all the files except with the name Tableau Reader 2019.2.app and from that using grep all files containing Tableau Reader are separated and then deletes it.

This is achievable with the find command only, but lets test it before it deletes something:
find "$HOME/Applications/" -maxdepth 1 -type f -name 'Tableau Reader*.app' -not -name 'Tableau Reader 2019.2.app'
See if it returns the list of the files you want to delete only:
The list of files should look like this, with your home directory instead of /tmp:
/tmp/Applications/Tableau Reader 10.5.app
/tmp/Applications/Tableau Reader 2012.app
/tmp/Applications/Tableau Reader 10.3.app
/tmp/Applications/Tableau Reader 1.app
Lets see the options used to drive find:
find: the command to find files
"$HOME/Applications/": the directory to start the search
-maxdepth 1: stay in the directory, do not go into sub-directories
-type f: find regular files
-name 'Tableau Reader*.app': find files with names matching this pattern
-not -name 'Tableau Reader 2019.2.app' and not matching this other pattern
Now if you are satisfied with the list and are sure that you want to delete these files; find can do it for you if you add it the -delete option like this:
find "$HOME/Applications/" -maxdepth 1 -type f -name 'Tableau Reader*.app' -not -name 'Tableau Reader 2019.2.app' -delete

How about something like
rm -f $(find . -name "tableau reader*" -type f -exec ls -t {} \; | head -n +2)
Essentialy, run a subshell (as can be seen between $( and ) to obtain what to remove. Within that subshell, use find in the current directory only (.) with result type of file (f) exec an ls in time modified order using the -t flag. Then pipe to head starting at the 2nd result (-n +2) (which ensures your latest file is excluded from the rm command.
Note - you have to be careful with ls though as you'll get strange results if filenames contain some special characters, but for your use-case it looks like it should be fine.
What I do with these tasks is copy some test files to a new directory to get my expression perfect before scripting it. Make sure you use cp -a to preserve the file dates though.

Related

how to specify 2 file name conditions in shell "find" cmd?

I need to search for all files - in cur dir and all subdirs with a name ending by ~, or a name that start and end by #, delete all files found.
this not working, it does not display found files
find -type f -name "~" or "#*#" ls -a -delete
and this is not working with piping:
find -type f -name "~" or "#*#" | ls -a | -delete
how to specify both conditions?
EDIT: The command line will find files matching the condition, print and then delete them.
Use -o (=or; the implicit default is -a = and) to combine operators and \(/\) to specify operator precedence:
find -type f \( -name "~" -o -name "#*#" \) -delete
You can combine multiple actions, e.g. outputting and deleting at the same time by simply providing all required actions:
find -type f \( -name "~" -o -name "#*#" \) -print -delete
An other option would be to simply filter the results with grep
find -type f | grep -E '(~|#*#)' | xargs rm
That would also allow to build additional steps (like displaying the files) into it

how to cp files with spaces in the filename when files are provided by find

I would like to ensure that all files found by find with a given criteria are properly copied to the required location.
$from = '/some/path/to/the/files'
$ext = 'custom_file_extension'
$dest = '/new/destination/for/the/files/with/given/extension'
cp 'find $from -name "*.$ext"' $dest
The problem here is that, when a file found with the proper extension and it is containing space cp cannot copy it properly.
You don't do that. You can't splat filenames with spaces that way.
You either get to use something from http://mywiki.wooledge.org/BashFAQ/001 to read the output from find line-by-line or into an array or you use find -exec to do the copy work.
Something like this:
from='/some/path/to/the/files'
ext='custom_file_extension'
dest='/new/destination/for/the/files/with/given/extension'
find "$from" -name "*.$ext" -exec cp -t "$dest" {} +
Using -exec command + here means that find will only execute as many cp commands as it needs based on command length limits. Using -exec command ; here would run one cp-per-file-found (but is more portable to older systems).
See comment from gniourf_gniourf about the use of -t in that cp command to make -exec command + work correctly.
Use -exec:
find "$from" -name "*.$ext" -exec cp {} "$dest" \;
you need to copy file one by one:
for file in "$from"/*."$ext"; do
cp "$file" "$dest"
done
I just use glob here, and it's enough and complete. I think find may introduce problem if the file name contains funny character.
The solution for this sort of problem is xargs -0 and the -print0 flag for find.
-print0 instructs find to print the results with a NUL character termination, instead of a newline, while -0 for xargs tells it expect input in that format.
Finally, the -J option for xargs allows one to put the arguments in the right place for a copy.
find "$from" -name "*.$ext" -print0 | xargs -0 -J % cp % "$dest"
It's better to use -exec argument of find command to do this:
find . -type f -name "*.ext" -exec cp {} ./destination_dir \;
I've checked this case with files containing spaces and it's work for me. Also don't forger to point out '-type f' if you want to find only files, not directories.

Recursively prepend text to file names

I want to prepend text to the name of every file of a certain type - in this case .txt files - located in the current directory or a sub-directory.
I have tried:
find -L . -type f -name "*.txt" -exec mv "{}" "PrependedTextHere{}" \;
The problem with this is dealing with the ./ part of the path that comes with the {} reference.
Any help or alternative approaches appreciated.
You can do something like this
find -L . -type f -name "*.txt" -exec bash -c 'echo "$0" "${0%/*}/PrependedTextHere${0##*/}"' {} \;
Where
bash -c '...' executes the command
$0 is the first argument passed in, in this case {} -- the full filename
${0%/*} removes everything including and after the last / in the filename
${0##*/} removes everything before and including the last / in the filename
Replace the echo with a mv once you're satisfied it's working.
Are you just trying to move the files to a new file name that has Prepend before it?
for F in *.txt; do mv "$F" Prepend"$F"; done
Or do you want it to handle subdirectories and prepend between the directory and file name:
dir1/PrependA.txt
dir2/PrependB.txt
Here's a quick shot at it. Let me know if it helps.
for file in $(find -L . -type f -name "*.txt")
do
parent=$(echo $file | sed "s=\(.*/\).*=\1=")
name=$(echo $file | sed "s=.*/\(.*\)=\1=")
mv "$file" "${parent}PrependedTextHere${name}"
done
This ought to work, as long file names does not have new line character(s). In such case make the find to use -print0 and IFS to have null.
#!/bin/sh
IFS='
'
for I in $(find -L . -name '*.txt' -print); do
echo mv "$I" "${I%/*}/prepend-${I##*/}"
done
p.s. Remove the echo to make the script effective, it's there to avoid accidental breakage for people who randomly copy paste stuff from here to their shell.

Exclude list of files from find

If I have a list of filenames in a text file that I want to exclude when I run find, how can I do that? For example, I want to do something like:
find /dir -name "*.gz" -exclude_from skip_files
and get all the .gz files in /dir except for the files listed in skip_files. But find has no -exclude_from flag. How can I skip all the files in skip_files?
I don't think find has an option like this, you could build a command using printf and your exclude list:
find /dir -name "*.gz" $(printf "! -name %s " $(cat skip_files))
Which is the same as doing:
find /dir -name "*.gz" ! -name first_skip ! -name second_skip .... etc
Alternatively you can pipe from find into grep:
find /dir -name "*.gz" | grep -vFf skip_files
This is what i usually do to remove some files from the result (In this case i looked for all text files but wasn't interested in a bunch of valgrind memcheck reports we have here and there):
find . -type f -name '*.txt' ! -name '*mem*.txt'
It seems to be working.
I think you can try like
find /dir \( -name "*.gz" ! -name skip_file1 ! -name skip_file2 ...so on \)
find /var/www/test/ -type f \( -iname "*.*" ! -iname "*.php" ! -iname "*.jpg" ! -iname "*.png" \)
The above command gives list of all files excluding files with .php, .jpg ang .png extension. This command works for me in putty.
Josh Jolly's grep solution works, but has O(N**2) complexity, making it too slow for long lists. If the lists are sorted first (O(N*log(N)) complexity), you can use comm, which has O(N) complexity:
find /dir -name '*.gz' |sort >everything_sorted
sort skip_files >skip_files_sorted
comm -23 everything_sorted skip_files_sorted | xargs . . . etc
man your computer's comm for details.
This solution will go through all files (not exactly excluding from the find command), but will produce an output skipping files from a list of exclusions.
I found that useful while running a time-consuming command (file /dir -exec md5sum {} \;).
You can create a shell script to handle the skipping logic and run commands on the files found (make it executable with chmod, replace echo with other commands):
$ cat skip_file.sh
#!/bin/bash
found=$(grep "^$1$" files_to_skip.txt)
if [ -z "$found" ]; then
# run your command
echo $1
fi
Create a file with the list of files to skip named files_to_skip.txt (on the dir you are running from).
Then use find using it:
find /dir -name "*.gz" -exec ./skip_file.sh {} \;
This should work:
find * -name "*.gz" $(printf "! -path %s " $(<skip_files.txt))
Working out
Assuming skip_files has a filename on each line, you can get the list of filenames via $(<skip_files.txt). E.g. echo $(<skip_files.txt) should print them all out.
For each filename you want to have a ! -path filename expression. To build this, use $(printf "! -path %s " $(<skip_files.txt))
Then, put it together with a filter on -name "*.gz"

In Linux terminal, how to delete all files in a directory except one or two

In a Linux terminal, how to delete all files from a folder except one or two?
For example.
I have 100 image files in a directory and one .txt file.
I want to delete all files except that .txt file.
From within the directory, list the files, filter out all not containing 'file-to-keep', and remove all files left on the list.
ls | grep -v 'file-to-keep' | xargs rm
To avoid issues with spaces in filenames (remember to never use spaces in filenames), use find and -0 option.
find 'path' -maxdepth 1 -not -name 'file-to-keep' -print0 | xargs -0 rm
Or mixing both, use grep option -z to manage the -print0 names from find
In general, using an inverted pattern search with grep should do the job. As you didn't define any pattern, I'd just give you a general code example:
ls -1 | grep -v 'name_of_file_to_keep.txt' | xargs rm -f
The ls -1 lists one file per line, so that grep can search line by line. grep -v is the inverted flag. So any pattern matched will NOT be deleted.
For multiple files, you may use egrep:
ls -1 | grep -E -v 'not_file1.txt|not_file2.txt' | xargs rm -f
Update after question was updated:
I assume you are willing to delete all files except files in the current folder that do not end with .txt. So this should work too:
find . -maxdepth 1 -type f -not -name "*.txt" -exec rm -f {} \;
find supports a -delete option so you do not need to -exec. You can also pass multiple sets of -not -name somefile -not -name otherfile
user#host$ ls
1.txt 2.txt 3.txt 4.txt 5.txt 6.txt 7.txt 8.txt josh.pdf keepme
user#host$ find . -maxdepth 1 -type f -not -name keepme -not -name 8.txt -delete
user#host$ ls
8.txt keepme
Use the not modifier to remove file(s) or pattern(s) you don't want to delete, you can modify the 1 passed to -maxdepth to specify how many sub directories deep you want to delete files from
find . -maxdepth 1 -not -name "*.txt" -exec rm -f {} \;
You can also do:
find -maxdepth 1 \! -name "*.txt" -exec rm -f {} \;
In bash, you can use:
$ shopt -s extglob # Enable extended pattern matching features
$ rm !(*.txt) # Delete all files except .txt files

Resources