As the title says, I just started with linux mint and am falling in love with bash scripts 😍 Actually I’m not sure if it’s considered a script, but I want to delete the last 2 files in all subfolders in a folder. So far I’ve (after great effort) got the terminal to list the files, but I want to delete them. Here is how I get them listed:
for f in *; do ls $f | tail -n 2; done
All their names come satisfyingly up in the terminal. Now what? I tried adding | xargs rm but that didn’t delete them. I also tried something with find command but that didn’t work either. Some folders have 3 items, so I want to delete #2 and 3. Some folders have 15 items so I want to delete #14 and 15. Folders are arranged by name, so it’s always the last 2 that I want to delete.
It’s frustrating to be sooooo clooooose, but also very fun. Any help is appreciated!
EDIT: Thanks for the awesome help guys! The next part of this is to move all the .html files into one folder (named “done”), prepending their name with an integer. So far I got:
n=1; for f in *; do find ./"$f" -type f | sort | xargs mv done/"$n$f"; n=$((n+1)); done
but that is… not really doing anything. The closest I have gotten so far is some error like
mv: Missing destination file operand
Any help is again appreciated!
You’re welcome! Happy I could help.
One other quick note, do the filenames or directories have spaces in them? If they do, that will cause a problem with the command as it is and need some additional modification. I accounted for the possible spaces in the directory names with the find command, but not with
xargs
. I just realized that as I was looking it over again.That was it! Thank you. I got rid of over 150 files in 127 directories with a lot less clicks than through the file explorer.
Luckily this time there were no spaces in the names. Spaces in names are a PITA at my stage of learning, and I’m never sure if I should use ’ or ".
Btw, new challenge in the edited original post, if you haven’t yet exhausted your thinking quota for the day lol.
I’d tackle this as two different commands rather than trying to come up with a oneliner to handle moving and renaming with incrementing numbers all in one go. It could be done all in one go, but to accomplish that, I’d probably write a bash script over a one liner.
So we’ll start with moving them, which is the easy part using
find
:find html-dir -name '*.html' -exec mv {} /path/to/new/dir/ \;
Let’s look at each argument:
find
- calls findhtml-dir
- is the path of the top level directory that contains all of the html files and/or other directories that contain the html files you want to move. This command will go recursively through all directories it finds inside of this top level directory.-name
- this flag tellsfind
to use the following expression to match filenames'*.html'
- This is the expression being used to find filenames. It’s using globbing to match any file that has the.html
extension-exec
this is kind of likexargs
, but for find. It allows you to take what has been found and act on it.mv
- the move command to move the files to the new directory{}
- this is the placeholder to call the find results/path/to/new/dir/
- the directory you want to move the.html
files to\;
- this terminates theexec
action. The backslash escapes the termination character. You can use two different termination characters,;
and+
. The semicolon tells exec to pass the results through one at a time, while the plus tells exec to pass the results through all at once.Once all of the files are in the new folder,
cd
into that folder and use this to enumerate the files:n=1; for file in *; do pad="$(printf '%02d' $n)"; mv $file $pad$file; ((n++)); done
This is a one liner similar to what you were trying to do.
n=1;
- sets our first number for the enumeration with a semicolon to terminate and set up the next commandfor file in *; do
- Sets up ourfor
loop.file
will be the variable in the loop representing each file as it loops, and*
will bring in all the.html
files in the directory you are currently in, and; do
terminates and sets up the loop.pad="$(printf '%02d"'' $n)";
- You may or may not want to use this. I like to pad my numbers so they list properly. This creates a new variable inside the loop calledpad
. Using command substitution, theprintf
command will take our$n
variable and pad it. The padding is dictated by the'%02d'
, which will turn1
into01
, and so on. If there will be more than 99.html
files, and you want padding to three digits, then change'%02d'
to'%03d'
. Again the semicolon terminates and sets up the next commandmv $file $pad$file;
- this is pretty straightforward. It’s taking the file being acted on in the loop as represented by the variable$file
and moving it inside the same directory with themv
command to give it a new enumerated name by concatenating the$pad
and$file
variables into a single filename. soa.html
would become01a.html
. This command is also terminated with a semicolon((n++));
- this is an easy way in bash to increment a numeric variable, again terminated by a semicolon,done
ends the loop, and the command.Let me know how it works for you!
😍😍😍😍 thanks harsh!! I’ll study this and report back. I really appreciate your time and effort. There is a lot to learn here, and actually the padding is on my list of things to learn, so thank you sensei! As to your question about the integers, the files need to be in alphabetical order before getting the integer prepended to them, so like
turns to
that way in the folder when it’s all said and done I’ll have
I’ll check if your method works out of the box for that or if I have to use the sort function like you showed me last time. Thanks again!
It won’t work the way you need it to as is, since the find command will first move all of those files together into the same folder, and if there are similar names between folder, like:
Then it will throw errors and/or overwrite files.
I’m at work so I can’t loom at this right now but I’ll look at it later when I get home.
Yea, I just came back to say this. Since cp overwrites by default (I tried copying first before trying moving) and each folder has files named index001 index002 etc then then folder where they all go has only ONE of index001.html, ONE of index002.html etc. So I think what I need to do is find each html file, rename it with a unique integer in front of the name, move it to the common folder.
Okay, took me awhile to write everything up. The script itself is pretty short, but it’s still much easier to do in a script than to try to make this a one line command.
I tested this creating a top level directory, and then creating three subdirectories inside it with a different number of html files inside those directories, and it worked perfectly. I’m going to break down exactly what’s going on in the script, but do note the two commented commands. I set this script up so you can test it before actually executing it on your files. In the breakdown of the script I’m going to ignore the testing command as if it were not in the script.
The script:
#! /bin/bash # script to move all html files from a series of directories into a single directory, and rename them as they are moved by adding a numeric indicator # to the beginning of the filename, while keeping files of the same folder grouped. fileList="$(find ~/test -name '*.html' | sort)" num=1 while IFS= read -r line; do pad="$(printf '%03d' $num)" #The below echo command will test the script to ensure it works. #The output to the terminal will show the mv command for each file. #If the results are what you want, you can comment this line out to disable the command, or delete it entirely. echo "mv $line ~/done/"$pad${line##*/}"" #This commented out mv command will actually move and rename all of the files. #When you are certain based on the testing that the script will work as desired #uncomment this line to allow the command to run and move & rename the files. # mv $line ~/done/"$pad${line##*/}" ((num++)) done<<<"$fileList"
The breakdown of the script is in the reply comment. Lemmy wouldn’t let me post it as one comment.
The breakdown:
#! /bin/bash
- This heads every bash script and is necessary to tell your shell environment what interpreter to use for the script. In this case we’re using/bin/bash
to execute the script.fileList="$(find ~/path/to/dir/with/html/files -name '*.html' | sort)"
- What this command is doing is creating a variable calledfileList
using command substitution. Command substitution encloses a command in"$()"
to tellbash
to execute the command(s) contained within the substitution group and save the output of the command(s) to the variable. In this case the commands are afind
command piped into asort
command.find ~/path/to/dir/with/html/files -name '*.html' | sort
- So this is the command set that will execute and the output of this command will be saved to the variablefileList
.num=1
- Here we’re creating a variable callednum
with a value of1
. This is for adding sequential numbers to the files as they are moved from their source directory to the destination directory.while IFS= read -r line; do
- This script uses awhile
loop to process each item saved to thefileList
variable. Within thewhile
loop, the moving and renaming of the files will take place.pad="$(printf '%03d' $num)"
- This is another variable being created using command substitution. What the command in the substitution group does is take thenum
variable and pad it with zeroes to be a three digit number.printf '%03d' $num
- This is the command that runs inside the substitution set.mv $line /path/to/dest/"$pad${line##*/}"
- This command actually moves and renames the file.((num++))
- Is a nice easy way to increment a number variable inbash
done<<<"$fileList"
- Is three parts.done
indicates the end of the while loop.<<<
is forheredoc
, which is a bash utility that allows you to pass a multiline chunk of text into a command. To pass a variable into a command withheredoc
you need to use three less than symbols (<<<
). Finally, is the variable holding the chunk of text we want fed into thewhile
loop, which is thefileList
variable (double quoted to insure proper expansion ignoring spaces and other nonstandard characters).And that’s the script! Let me know how it works for you.
Agree. I think for that a bash script will be a better approach. I’ll be off work in a few hours. I’ll take a look then.
Okay, for the HTML files, do the integers need to be apied to the files in a certain order, like, alphanumerically from the folders they’re coming from?