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!

  • @[email protected]
    link
    fedilink
    42 days ago

    Here’s the command to delete the files:

    for f in *; do find ./"$f" -type f | sort | tail -n 2 | xargs -n 1 rm; done

    If you want to insure it will target the correct files, first run this command (I HIGHLY recommend you do this first. Verify BEFORE you delete so you don’t lose data):

    for f in *; do find ./"$f" -type f | sort | tail -n 2; done

    I’ll be adding another comment reply with a breakdown of the command shortly (just need to write it up)

    • @[email protected]
      link
      fedilink
      5
      edit-2
      2 days ago

      Here’s what’s happening in the command;

      for f in *; do

      You already know this for loop, which is using the * glob to iterate over each directory in the current directory.

      find ./"$f" -type f

      Instead of your original ls command, which gives the file names, and not their full paths, we’re using GNU find, which outputs the full path of what it finds. The arguments are:

      ./"$f" - This tells find where to start its search. I double qouted the $f variable to properly expand the directory name even if it has nonstandard characters in it like spaces.

      -type f - This tells find what kind of file object to look for. So it’s two parts. -type to tell find there will be a specific type to look for, and the f flag, which means file. Meaning, it will only find files

      The output of find is not sorted alaphabetically, so before piping the output to tail, we first pipe it to sort, which by default will sort alphanumerically, which we then pipe to tail to grab just the last two files, and finally we get to the xargs bit.

      Here I added the -n 1 argument to xargs to get it to work on the files one at a time. This isn’t actually necessary. You could just run it as xargs rm. I didn’t realize that before I posted the command. (I’m still learning too! The learning never ends. :D )

      • @[email protected]OP
        link
        fedilink
        English
        22 days ago

        Thanks so much harsh!!! I will study this and hit Enter after I understand it.

        Thanks again, that’s epic.

        • @[email protected]
          link
          fedilink
          32 days ago

          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.

          • @[email protected]OP
            link
            fedilink
            English
            22 days ago

            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.

            • @[email protected]
              link
              fedilink
              22 days ago

              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 find

              html-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 tells find 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 like xargs, 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 the exec 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 command

              for file in *; do - Sets up our for 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 called pad. Using command substitution, the printf command will take our $n variable and pad it. The padding is dictated by the '%02d', which will turn 1 into 01, 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 command

              mv $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 the mv command to give it a new enumerated name by concatenating the $pad and $file variables into a single filename. so a.html would become 01a.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!

              • @[email protected]OP
                link
                fedilink
                English
                1
                edit-2
                1 day ago

                😍😍😍😍 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

                • folder1/file1
                • folder1/file2
                • folder2/file1
                • folder2/file2
                • folder2/file3

                turns to

                • folder1/001file1
                • folder1/002file2
                • folder2/003file1
                • folder2/004file2
                • folder2/005file3

                that way in the folder when it’s all said and done I’ll have

                • 001file1
                • 002file2
                • 003file1
                • 004file2
                • 005file3

                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!

                • @[email protected]
                  link
                  fedilink
                  11 day ago

                  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:

                  folder1/file1
                  folder2/file1
                  

                  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.

                  • @[email protected]OP
                    link
                    fedilink
                    English
                    21 day ago

                    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.

            • @[email protected]
              link
              fedilink
              12 days ago

              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?