I have a folder structure like this
a/
-b/
-c.txt
-d.txt
-backups/
I want to move the contents of folder a into backups so the folder structure is this.
a/
-b/
-c.txt
-d.txt
-backups/
-b/
-c.txt
-d.txt
Here are the commands I have used so far.
for d in a/*/ ; do
mkdir -p ${d}backups/
cp -ra ${d}* backups
done
I make the folder backups then I try to copy the content into the backups folder. However, I get the error: CP Hardlink cannot copy folder onto itself. How can i do this?
Thank You
a
├── b
├── backups
├── c.txt
└── d.txt
2 directories, 2 files
Enable extglob
by shopt -s extglob and execute cp -r !(backups/) backups. The following will be the result:
a
├── b
├── backups
│ ├── b
│ ├── c.txt
│ └── d.txt
├── c.txt
└── d.txt
3 directories, 4 files
it is trying to copy "backups" into "backups" , so you need to make sure you exclude "backups" from the a/*/ pattern.
you should probably use "find" to find files with a given pattern and exclude the "backup" directory. With find you can do "-not -name backup"
Related
I'm trying to utilize a bash script to delete some unwanted files with the same name in different directories, eg: text1.txt exists in multiple directories and I wish to remove it in every directory it exists in.
I need the script to delete the unwanted files and then also delete the directory in which that filename 'text1.txt' exists, so if it exists in a folder named 'TextFiles' I need that folder directory to be deleted.
This is my current code I'm working on:
for files in "/*"
do
rm file.1txt file2.txt file3.txt
I'm a bit curious about whether the "/*" will look into all directories and whether the 'do' is working to remove the files stated.
Also, after utilising the 'rm' to remove specific files how do I delete the directory it exists in.
Many thanks!
Before I start, I have to note that the rm command can do some nasty things in your system. Automating it can lead to unintended data loss (system or personal files and folders) if used carelessly.
Now that I said that, imagine the following file structure:
bhuiknei#debian:~/try$ tree
.
├── dir1
│ └── this.txt
└── dir2
├── dir3
│ ├── this
│ └── this.txt
├── notthis.txt
└── this.txt
3 directories, 5 files
To find and filter specific files find and grep are your friends. The "-w" option will match to whole words only (so the notthis.txt is not picked up):
bhuiknei#debian:~/try$ find . | grep -w this.txt
./dir1/this.txt
./dir2/dir3/this.txt
./dir2/this.txt
Now that we have all paths for the files lined up, these can be piped into a while loop where we can delete the files one-by-one. Then the empty directories can be deleted in a second step.
I would not suggest deleting the containing folders forcibly as they might contain other files and folders too.
The following script does the trick:
#!/bin/bash
#Exiting if no file name was given
[[ $# -ne 1 ]] && { echo "Specify a filename to delete in all sub folders"; exit 1; }
#Deleting files matching input parameter
echo "Deleting all files named ${1} in current and sub-directories."
find . | grep -w "$1" | \
while IFS= read LINE; do
rm -v "$LINE"
done
#Deleting only-empty folders
rmdir -v *
exit 0
And the result:
bhuiknei#debian:~/try$ tree
.
├── dir1
│ └── this.txt
├── dir2
│ ├── dir3
│ │ ├── this
│ │ └── this.txt
│ ├── notthis.txt
│ └── this.txt
└── script
3 directories, 6 files
bhuiknei#debian:~/try$ ./script this.txt
Deleting all files named this.txt in current and sub-directories.
removed './dir1/this.txt'
removed './dir2/dir3/this.txt'
removed './dir2/this.txt'
rmdir: removing directory, 'dir1'
rmdir: removing directory, 'dir2'
rmdir: failed to remove 'dir2': Directory not empty
rmdir: removing directory, 'script'
rmdir: failed to remove 'script': Not a directory
bhuiknei#debian:~/try$ tree
.
├── dir2
│ ├── dir3
│ │ └── this
│ └── notthis.txt
└── script
2 directories, 3 files
Also a side note: I didn't test what happens if the working directory is different where the script is located, so make sure to run it locally from the parent dir, or add some protection. Working with absolute paths can be a solution.
Good luck!
You know the extension of the file name and so you can utilise this in a loop parsing the output of find with parameter expansion and so:
find /path -name "file1.txt" | while read var
do
echo "rm -Rf ${var%/file1.txt}" # echo the command
# rm -Rf "${var%/file1.txt}" # execute the command when sure that command list as expected
done
${var%/file1.txt} -
will expand the output from find and expand the output only up to /file1.txt (the directory) rm -Rf will then force removal the directory along with the file
Alternatively you can use printf natively in find to print only the directory without the file:
find /path -name "file1.txt" -printf "%h\n" | while read var
do
echo "rm -Rf $var" # echo the command
# rm -Rf "$var" # execute the command when sure that command list as expected
done
I'm hoping someone can help me, I'm looking for help with a bash script to move files by file extension from multiple directories, one directory deeper within their respective directories.
For example I have a 'Projects' directory, that has multiple directories within in it 'Project 001, Project 002, Project 003' and so on. I have '.JPG' files in those folders and want to put them in a 'JPG' folder within their respective project folder - how would I do this with a bash script as there are too many to do by hand?
To make matters more complicated some of those project folders already have 'JPG' folders in them, and some of those have some of the files already in them as duplicates, so I think I'd need to add an overwrite confirmation to it too based on filename.
I know a little bash, but this is a little over my head, so any help would be greatly appreciated.
Thanks,
/ Hami
This is using a CLI on Ubuntu Server 18.04 LTS, on a disk with thousands of directories with human filenames - spaces, parenthesis, and varied unicode character such as Japanese, and European. The names of the directories are varied, and have no particular formula.
Ideally, I'd like to go from this:
Projects
Project 001
Image 001.jpg
Project 002
Image 002.jpg
Image 003.jpg
Project 003
...to this:
Projects
Project 001
JPG
Image 001.jpg
Project 002
JPG
Image 002.jpg
Image 003.jpg
Project 003
You can do what you need with find that searchers for all *.jpg files and a simple helper script called by the -exec option to find to create the jpg directory and move all .jpg files into the new directory.
The helper script will simply get the absolute filename utilitzing readlink -f and then using quick parameter expansion to trim the last /... component from the absolute filename to obtain the full path. Then it is simply a matter of creating the jpg directory at the end of the path and moving the file to the new directory.
Your helper script (I called it helper.sh) could be:
#!/bin/sh
test -z "$1" && exit ## validate 1 argument given or exit
full=$(readlink -f "$1") ## get full filename
dir="${full%/*}" ## get full path
test "${full##*.}" = 'jpg' || exit ## test extension is jpg or exit
test -z "$dir" && dir="/" ## check if file was in / (root)
test -d "$dir/jpg" || mkdir -p "$dir/jpg" ## check/create jpg dir at end of path
mv "$full" "$dir/jpg" ## move file into new jpg dir
(note: after creating the helper script, make sure you make it executable with chmod +x helper.sh)
Original Projects Directory Tree
$ tree Projects/
Projects/
├── Project_001
│ └── Image_001.jpg
├── Project_002
│ ├── Image_002.jpg
│ └── Image_003.jpg
└── Project_003
Your find command operating on the Projects directory calling the helper script for each file would be:
$ find Projects/ -type f -name "*jpg" -exec ./helper.sh '{}' \;
Resulting Projects Directory Tree
$ tree Projects/
Projects/
├── Project_001
│ └── jpg
│ └── Image_001.jpg
├── Project_002
│ └── jpg
│ ├── Image_002.jpg
│ └── Image_003.jpg
└── Project_003
Let me know if you have further questions.
Preserving Files Already In jpg Directory
Per-your additional comment, in order to preserve .jpg files already within a jpg directory under your Projects, all you need to do is add one additional check. If the last component of the path is already jpg, just exit the helper, e.g.
test "${dir##*/}" = 'jpg' && exit ## if already in jpg dir, exit
Shown in context in helper.sh:
test -z "$1" && exit ## validate 1 argument given or exit
full=$(readlink -f "$1") ## get full filename
dir="${full%/*}" ## get full path (trim last /*)
test "${dir##*/}" = 'jpg' && exit ## if already in jpg dir, exit
test "${full##*.}" = 'jpg' || exit ## test extension is jpg or exit
...
Original Projects Directory Tree (w/existing jpg)
$ tree Projects/
Projects/
├── Project_001
│ ├── Image_001.jpg
│ └── jpg
│ └── Image_000.jpg
├── Project_002
│ ├── Image_002.jpg
│ ├── Image_003.jpg
│ └── jpg
│ ├── Image_000.jpg
│ └── Image_001.jpg
└── Project_003
Resulting Projects Directory Tree
$ tree Projects/
Projects/
├── Project_001
│ └── jpg
│ ├── Image_000.jpg
│ └── Image_001.jpg
├── Project_002
│ └── jpg
│ ├── Image_000.jpg
│ ├── Image_001.jpg
│ ├── Image_002.jpg
│ └── Image_003.jpg
└── Project_003
Maybe one of you guys has something like this at hand already? I tried to use robocopy on windows but to no avail. I also tried to write a bash script in linux with find etc... but gave up on that one also ^^ Google search brought no solution also unfortunately. I need this for my private photo library.
Solution could be linux or windows based, both are fine. Any ideas?
I would like to get rid of hundreds of 'intermediary folders'.
I define an 'intermediary folder' as a folder that contains nothing else than exactly one sub-folder. Example
folder 1
file in folder 1
folder 2 <-- 'intermediary folder: contains exactly one sub-folder, nothing else'
folder 3
file in folder 3
What I would like to end up with is:
folder 1
file in folder 1
folder 3
file in folder 3
I do not need the script to be recursive (removing several layers of intermediary folders at once), I'll just run it several times.
Even cooler would be if the script could rename folder 3 in the above example to 'folder 2 - folder 3', but I can live without this feature I guess.
I guess one of you linux experts has a one liner handy for that? ^^
Thank you very much!
Take a look at this code:
#!/usr/bin/env bash
shopt -s nullglob
while IFS= read -rd '' dir; do
f=("$dir"/*)
if ((${#f[#]}==1)) && [[ -d $f ]]; then
mv -t "${dir%/*}" "$f" || continue
rm -r "$dir"
fi
done < <(find folder1 -depth -mindepth 1 -type d -print0)
Explanation:
shopt -s nullglob: allows filename patterns which match no files to expand to a null string
find ... -depth: makes find traverse the file system in a depth-first order
find ... -mindepth 1: processes all directories except the starting-point
find ... -type d: finds only directories
find ... -print0: prints the directories separated by a null character \0 (to correctly handle possible newlines in filenames)
while IFS= read ...: loops over all the directories (the output of find)
f=("$dir"/*): creates an array with all files in the currently processed directory
((${#f[#]}==1)) && [[ -d $f ]]: true if there is only one file and it is a directory
mv -t "${dir%/*}" "$f": moves the only subdirectory one directory above
mv ... || continue: mv can fail if the subdirectory already exists in the directory above. || continue ignores such subdirectory
rm -r "$dir": removes the processed directory
Test run:
$ tree folder1
folder1
├── file1
├── folder2
│ └── folder3
│ └── file3
├── folder4
│ ├── file4a
│ ├── file4b
│ └── file4c
└── folder5
└── folder6
├── file6
└── folder7
└── folder8
└── folder9
├── dir9
└── file9
$ ./script
$ tree folder1
folder1
├── file1
├── folder3
│ └── file3
├── folder4
│ ├── file4a
│ ├── file4b
│ └── file4c
└── folder6
├── file6
└── folder9
├── dir9
└── file9
I'm looking to make a crontab that will search through a directory and all subdirectories and find all files with extension *.mkv then move them to a different directory and create an empty file with the same name and extension in place of the original file.
So it would look like this:
find *.mkv in subdirectories of /home/user/directoryA/~
move *.mkv to /home/user/directoryB/
create empty *.mkv with same filename as the original in place of file in /home/user/directoryA/~
What would be the best way to accomplish this?
The process isn't too difficult if you recognize that when forming your new directory names, your old base directory will simply be a substring within the new directory name. Bash provides a parameter expansion with substring replacement that is tailor made for this process.
Essentially, you find each file below your source directory with the *.mkv extension, you use parameter expansion with substring replacement to form the new full-filename containing your destination directory, (e.g. nffn="${ffn/$srcdir/$destdir}", where ffn is short for full-filename and nffn short for new full-filename)
With your new full-filename formed containing the updated path, it is just a matter of making sure the destination directory exists before moving the file. mkdir -p is perfect here as it will create the full path, and will not complain if the directory already exists. You simply use a parameter expansin with substring removal to isolate the new directory from the new full-filename to pass to mkdir -p, and finally, you check that mkdir -p succeeds or you handle the error, e.g.
## create new directory, handle error if create fails
mkdir -p "${nffn%/*}" || {
echo "error: creating '${nffn%/*}'" >&2
exit 1
}
Putting all the pieces together, you can do what you are attempting with a short script similar to the following.
#!/bin/bash
## source and destination directories, file pattern
# (note: to change destdir, two arguments required
# to change patrn, three arguments required)
srcdir="${1:-/home/david/dev/src-c/tmp/debug/AAA}"
destdir="${2:-/home/david/dev/src-c/tmp/debug/BBB}"
patrn="${3:-*.mkv}"
while read -r ffn; do ## loop over each full-filename
nffn="${ffn/$srcdir/$destdir}" ## form new full-filename
## create new directory, handle error if create fails
mkdir -p "${nffn%/*}" || {
echo "error: creating '${nffn%/*}'" >&2
exit 1
}
mv "$ffn" "$nffn" ## move full-filename to new full-filename
touch "$ffn" ## touch full-filename for zero original
done < <(find "$srcdir" -name "$patrn")
(note: you can pass the directories and file pattern as positional parameters, but note, if you pass more than 1, you must pass each required parameter (or you could implement getotp))
Initial Directories AAA & BBB
$ tree AAA
AAA
├── a.mkv
├── b.mkv
├── dir1
│ ├── a.mkv
│ └── b.mkv
├── dir2
│ ├── a.mkv
│ └── b.mkv
├── dir3
│ ├── a.mkv
│ └── b.mkv
└── dira
├── a.mkv
└── b.mkv
$ tree BBB
BBB [error opening dir]
Final Directories AAA & BBB
$ bash mvemptydir.sh
$ tree AAA
AAA
├── a.mkv
├── b.mkv
├── dir1
│ ├── a.mkv
│ └── b.mkv
├── dir2
│ ├── a.mkv
│ └── b.mkv
├── dir3
│ ├── a.mkv
│ └── b.mkv
└── dira
├── a.mkv
└── b.mkv
$ tree BBB
BBB
├── a.mkv
├── b.mkv
├── dir1
│ ├── a.mkv
│ └── b.mkv
├── dir2
│ ├── a.mkv
│ └── b.mkv
├── dir3
│ ├── a.mkv
│ └── b.mkv
└── dira
├── a.mkv
└── b.mkv
Look things over and let me know if you have further questions.
you can write a script like this :
#!/bin/bash
cd /[ADDRESS]
find . -name *.mkv > /tmp/find_result.txt
mv `cut -f1 /tmp/find_result.txt` /backup/
touch `cut -f1 /tmp/find_result.txt`
1- go to your directory that you want to find this files
2- find all .mkv files and send the result to a file like /tmp/find_result.txt in this example
3- move all files (that save in file "/tmp/find_result.txt") to your desired directory (like "/backup" in this example)
4- finaly create empty file with same name (that save in file "/tmp/find_result.txt")
you can add this script to crontab.
You could use a loop to do this for each file matching your criteria!
for f in `find . -name *.mkv`; do
mv $f /home/user/directoryB/
touch $f
done;
If you wanted to get fancy you could put this into a script and accept directoryA/B as arguments:
for f in `find $1 -name *.mkv`; do mv $f $2; touch $f; done;
and run as ./script.sh /home/user/directoryA/~ /home/user/directoryB/
I would like to move some files from one folder to another. Say I have:
├── bar
│ ├── a_folder
│ │ └── apicture.png
│ └── another_folder
│ └── myfile.txt
└── foo
And I would like to have every txt files moved from bar to foo:
├── bar
│ ├── a_folder
│ └── apicture.png
│ └── another_folder
└── foo
└── another_folder
└── myfile.txt
I tried using find command using:
find bar -type f -exec mv {} $temp/{} \;
This should work but only if folders are already existing in foo folder. Thus I am looking for a way to create these folders in foo, and it seems that mv does not do that. How can I achieve that?
cp -R bar/. foo/
find bar -type f -delete
Yes that involves a copy and isn't a one-liner, but unless we're talking huge amounts of data and a recurring job here, it's a good stop-gap. (Note the bar/. instead of the usual bar/*, as the latter would miss dot-files.)
I doubt there is a one-liner way to move the files but keep the directories anyway.
rsync has this capability built in.
cd bar && rsync --relative [--no-implied-dirs] . ../foo/
Use --no-implied-dirs if there are symlinks in the tree you want to keep as symlinks.
If you only want to move files where the destination directory already exists, use the --no-dirs flag.
rsync is not in the POSIX toolset, but it is common on UNIX-like machines including OS X and most Linuxen.