0

I wish to remove files using bash. Here's the scenario: In my current directory, I have some files and folder, among them there is one folder called pkgs. I copy the content of pkgs to parent directory, do some operations, now I want to delete the contents of pkgs (which got copied over to parent directory) and pkgs folder.

Here's how I am attempting:


# first I store the content in a variable
filesToKeep=$(ls -A)

# then copy over contents of pkgs
cp pkgs/* .

# do something

# remove pkgs
rm -rf pkgs

# then remove using find
find . ! -path "$filesToKeep" -delete

The last command I am unable to figure out, because that command deletes everything. If there is a better way, please let me know, appreciate your time and help.

monte
  • 1,482
  • 1
  • 10
  • 26
  • Better to move `pkgs` content to outside the current directory, do something, remove everything in current directory and then move back `pkgs` content. – anubhava Jan 13 '23 at 06:34
  • I do move `pkgs` content to current directory, but I want to then remove these contents of `pkgs` from current directory, while keeping the files which were already there – monte Jan 13 '23 at 06:40
  • I suggested `outside the current directory` not the current e.g. `$HOME/tmp` – anubhava Jan 13 '23 at 06:52
  • `-path` expects its argument to be a glob pattern for the files to find, but you pass to it the output of `ls -A`, which will be in general several files (separated by a space). Hence your `! -path ....` most likely will match every file, which means all of the files get deleted. It is not clear to me **what** you want to delete with the last command. – user1934428 Jan 13 '23 at 07:22
  • 1
    If you're going to save the output from `ls` to a variable with the intention of parsing it later, you'd probably be better off using an array. And to do that sanely you'll want to make sure only one item is listed per line. Something like this maybe. `filesToKeep=(); while read line; do filesToKeep+=("$line"); done < <(ls -A | column -c 1)` Then you can access an individual item like`"${filesToKeep[3]}"`. Or access every item with `"${filesToKeep[@]}"`. – I0_ol Jan 13 '23 at 07:51
  • `filesToKeep=$(ls -A)` -- this is wrong for several reasons. Read why [here](https://mywiki.wooledge.org/BashPitfalls#for_f_in_.24.28ls_.2A.mp3.29) – axiac Jan 13 '23 at 09:36

1 Answers1

2

A really simple solution is to remove pkgs after deleting files in the current directory which are also present in the subdirectory.

shopt -s nullglob dotglob  # Bash extension: see below
for f in pkgs/*; do
    rm ./${f#pkgs/}
done
rm -rf pkgs

Tangentially, don't use ls in scripts.

If you want to keep the files in a variable for some reason (like, maybe pkgs was removed for other reasons during some other processing which you did not elaborate on in your question; or maybe other temporary files are added during processing which should also be removed), use an array instead of a string variable. (This is not compatible with POSIX sh, but your question is tagged .)

shopt -s nullglob dotglob

filesToKeep=(./*)
:
: other processing ...
:
theseFiles=(./*)
for file in "${filesToKeep}"; do
    for i in "${!theseFiles[@]}"; do
        [[ "${theseFiles[i]}" = "$file" ]] && unset 'theseFiles[i]'
    done
done
rm "${theseFiles[@]}"

(Array outer join implemented based on Remove an element from a Bash array)

If you don't want to use shopt -s dotglob for some reason, you need to use multiple wildcards to include dot files in the results. Properly speaking, the correct expression would be something like

pkgs/..?* pkgs/.[!.]* pkgs/*

but in practice, you might want to exclude ..?* if you are lazy and/or know for certain that there will be no files whose names start with two dots. (You need to exclude the parent directory from the matches, but include other files which start with dots.)

shopt -s nullglob avoids having any of the wildcards expand to itself if there are no matches.

Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
tripleee
  • 175,061
  • 34
  • 275
  • 318
  • Can you please explain this `pkgs/.[!.]*`? – Renaud Pacalet Jan 13 '23 at 09:20
  • @RenaudPacalet Because the OP used `ls -A` and `pkgs/*` does not include dot files; the `.[!.]` pattern excludes the parent directory (as well as any other files with two dots; you'll have to add a third wildcard if you have actual files whose names start with two literal dots). – tripleee Jan 13 '23 at 09:23
  • 1
    Then you should probably have suggested to set `nullglob` first. If there are no `.*` files in `pkgs` what you suggest will delete all `.*` files in the parent directory... – Renaud Pacalet Jan 13 '23 at 09:28
  • 1
    Note that setting also `dotglob` would probably be even better (matching any dotfile but `.` and `..`). You could then simply `for f in pkgs/*; do...` – Renaud Pacalet Jan 13 '23 at 09:34
  • @RenaudPacalet +1 some more, updated again. – tripleee Jan 13 '23 at 09:39
  • Sorry to split hairs but `nullglob` is also needed, else, if `pkgs` is empty, you'll delete all non-dot files from the parent directory. – Renaud Pacalet Jan 13 '23 at 09:52
  • @RenaudPacalet Don't be worry; thanks again for the feedback, and your persistence (-: – tripleee Jan 13 '23 at 10:24
  • strangely when I run `filesToKeep=(./*)`, it is picking only one folder which starts with dot, but `ls` shows all the folders – monte Jan 13 '23 at 10:41
  • I have come to use this: `mkdir newFolder; cp $(ls -A | grep -v "pkgs") newFolder; cp pkgs/. .; ...do something ...; ls -A | grep -v "newFolder" | xargs rm -r `. This does work for my usecase, but not sure of any unintended consequences – monte Jan 13 '23 at 10:44
  • I'll repeat the advice to avoid `ls` in scripts. The page I linked to in the question explains the numerous oddities and corner cases where it breaks or produces incorrect results. With extended globbing (`shopt -s extglob`) you could remove everything except `newFolder` simply with `rm -r !(newFolder)` and similarly `cp !(pkgs) newFolder` for the copy. – tripleee Jan 13 '23 at 10:46