How to copy all files within a directory with globstar? - bash

Say I want to copy all files within dir to dest:
$ tree .
.
├── dest
└── dir
├── dir
│   ├── file1
│   └── file2
└── file3
This is easy if I know the filenames and the directory depths:
$ echo dir/f* dir/*/*
dir/file3 dir/dir/file1 dir/dir/file2
$ cp dir/f* dir/*/* dest/
$ tree dest/
dest/
├── file1
├── file2
└── file3
It's also easy (with globstar) to get only the directories:
$ echo dir/**/*/
dir/dir/
But I don't know how to glob only the files, e.g. the following doesn't work:
$ echo dir/**/*!(/)
dir/**/*!(/)

One option is to use find with -type f option:
find dir -type f -exec cp {} dest \;

Related

Take a list of names from a text file and compare them with a list of directories in Bash

I am trying to take a list of names from a text file and compare them with a list of directories. If there is a match in the directories then move them.
The code below doesn't work but it is essentially what I am trying to achieve.
#!/bin/bash
echo "Starting"
names="names.txt"
while IFS= read -r directory; do
find 'Folder/' -type d -name '$directory' -print0
done < "$names" | xargs -t mv Folder/ MoveTo/
Example folder structure:
Folder/
folder1
folder2
folder3
oddfolder
oddfolder2
MoveTo/
(empty)
Example text file structure:
folder1
folder2
folder3
Output expectation:
Folder/
oddfolder
oddfolder2
MoveTo/
folder1
folder2
folder3
I don't have an issue with spaces or capitalization. If there is a match then I want to move the selected folders to a different folder.
This should work:
$ tree
├── folder
│   ├── f1
│   ├── f2
│   ├── f3
│   ├── f4
│   ├── other1
│   └── other2
├── name.txt
└── newdir
$ cat name.txt
f1
f2
f3
f4
$ while IFS= read -r dir; do
mv "folder/$dir" newdir/. 2>/dev/null
done < name.txt
$ tree
.
├── folder
│   ├── other1
│   └── other2
├── name.txt
└── newdir
├── f1
├── f2
├── f3
└── f4
Note that you should also use " instead of ' with variable
You do not have to execute find command within the while loop.
The test [[ -d dirname ]] will be enough to confirm the existence
of the directory. Would you please try:
#!/bin/bash
names="names.txt"
src="Folder"
dest="MoveTo"
while IFS= read -r dir; do
[[ -d $src/$dir ]] && mv "$src/$dir" "$dest"
done < "$names"

Single line bash command to rename all files with name A to name B

Given the file and folder structure:
test
├── a
│   └── hans.x
├── b
│   └── hans.x
└── c
└── hans.x
I would like to use a single line bash command to rename all files "hans.x" to "peter.x".
Desired Result:
test
├── a
│   └── peter.x
├── b
│   └── peter.x
└── c
└── peter.x
I have looked at many solutions on SO and found lots of solutions that show how to rename the file "peter.x" to "peter.x_somethingmore", but never the above scenario.
I have tried the following:
find . -type f -name 'hans.x' -print0 | xargs --null -I{} mv {} "$(dirname "{}")"peter.x
But unfortunately, that results in the following execution:
mv ./test/a/hans.x .peter.x
mv ./test/c/hans.x .peter.x
mv ./test/b/hans.x .peter.x
With GNU find, you could use -execdir:
find -type f -name 'hans.x' -execdir mv {} peter.x \;
$(...) is expanded before xargs is executed. You want to run it all inside a subshell created when parsing the line:
... | xargs -0n1 sh -c 'mv "$1" "$(dirname "$1")"/hans.y' _

deleting intermediary folders

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

Bash - Combine files in separated sub folders

So I'm looking for a way to cat .html files in multiple subfolders, but by keeping them in their place.
Actual situation:
$ Folder1
.
├── Subfolder1
│ └── File1.html
└── File2.html
├── Subfolder2
│ └── File1.html
└── File2.html
Desired outcome:
$ Folder1
.
├── Subfolder1
│ └── Mergedfile1.html
└── File1.html
└── File2.html
├── Subfolder2
│ └── Mergedfile2.html
└── File1.html
└── File2.html
So far I've came up with this:
find . -type f -name *.html -exec cat {} + > Mergedfile.html
But this combines all the files of all the subfolders of Folder1, while I want to keep them separated.
Thanks a lot!
You can loop on all subfolders with a for statement:
for i in Folder1/SubFolder*; do
cat "$i"/File*.html > MergeFile$(echo "$i" | sed 's,.*\([0-9]\+\)$,\1,').html
done
Like told by AK_ , you can use find with exec.
find Folder1/ -mindepth 1 -maxdepth 1 -type d -exec sh -c "rep='{}';cat "'"$rep"'"/*.html > "'"$rep"'"/Mergedfile.html" \;

List directories and files in a tree structure

I'm trying to list directories and files contained within them in the following way:
DIR: name1
file1
file2
file3
DIR: name2
file4
file5
file6
DIR: name3
file7
So far, I've come up with a way to get a tree structure using find and sed, like this:
find . | sed -e "s/[^-][^\/]*\// |/g"
I don't know how to distinguish between files and directories to be able to add DIR: in front of the names of the directories.
Try this with tree and GNU sed:
tree -F coreutils-8.9 | sed -r 's|── (.*)/$|── DIR: \1|'
Output (example):
coreutils-8.9
├── ABOUT-NLS
├── bootstrap.conf
├── DIR: build-aux
│   ├── announce-gen*
│   ├── arg-nonnull.h
│   └── ylwrap*
├── cfg.mk
├── ChangeLog
├── DIR: doc
│   ├── ChangeLog-2007
│   └── constants.texi
└── TODO
I assume that the file names don't contain "── ".
If you don't have tree you can use GNU find to identify directories vs files:
$ find . -mindepth 1 -printf '%y %p\n'
d ./dir1
d ./dir1/dir2
f ./dir1/dir2/fileA
d ./dir1/dir3
f ./dir1/fileC
f ./fileB
and then parse the output with awk to create the indenting, etc.
$ find . -mindepth 1 -printf '%y %p\n' |
awk '$1=="d"{sub(/.*\//,"&DIR: ")} {gsub(/[^\/]*\//," ")} 1'
DIR: dir1
DIR: dir2
fileA
DIR: dir3
fileC
fileB

Resources