List directories and files in a tree structure - shell

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

Related

How to rename files recursively with Bash

I am trying to make a bash script that should replace any occurrence of a given pattern with an other, given, expression in any path in a given directory. For instance, if I have the following tree structure:
.
|- file1
|- file-pattern-pattern.html
|- directory-pattern/
| |- another-pattern
| \- pattern.pattern
\- other-pattern/
\- a-file-pattern
it should end up looking like
.
|- file1
|- file-expression-expression.html
|- directory-expression/
| |- another-expression
| \- expression.expression
\- other-expression/
\- a-file-expression
The main issue I have is that most solution I have found make either usage of the ** glob pattern alongside with a shopt -s globstar nullglob or find to execute rename on all the files, but since I actually change the name of a directory during that operation, it breaks with messages like find: ./directory-pattern: No such file or directory or rename: ./directory-pattern/another-expression: not accessible: No such file or directory.
The second issue is that, according to rename'a man page, it "will rename the specified files by repalcing the first occurrence" of the pattern, not all occurrences, and I didn't find any option to overwrite this behavior. Of course, I don't want to "just run rename with -v until it doesn't spit anything anymore", which just sounds silly.
So the question is: how do I achieve that bulk-renaming in Bash?
Edit: leave only the 1-pass solution that apparently works as well as the 2-passes.
You'll probably have to explore the hierarchy depth first. Example with find and a bash exec script:
$ find . -depth -name '*pattern*' -exec bash -c \
'f=$(basename "$1"); d=$(dirname "$1"); \
mv "$1" "$d/${f//pattern/expression}"' _ {} \;
Demo:
$ tree .
.
├── file-pattern-pattern.html
├── file1
├── foo-pattern
│   └── directory-pattern
│   ├── another-pattern
│   └── pattern.pattern
└── other-pattern
└── a-file-pattern
$ find . -depth -name '*pattern*' -exec bash -c \
'f=$(basename "$1"); d=$(dirname "$1"); \
mv "$1" "$d/${f//pattern/expression}"' _ {} \;
$ tree .
.
├── file-expression-expression.html
├── file1
├── foo-expression
│   └── directory-expression
│   ├── another-expression
│   └── expression.expression
└── other-expression
└── a-file-expression
Explanation: -depth tells find to process each directory's contents before the directory itself. This avoids one of the issues you encountered when referring to a directory that was already renamed. The bash script uses simple pattern substitutions to replace all occurrences of string pattern by string expression.

Script to replace/delete characters in direcory and file names (work in progress)

I am trying to delete a set of characters like single quote (') and spaces from file names and directories. Example, I have:
Directory I'm confused which contains file you're right
So far, I have been able to create a short script:
#!/bin/sh
for f in *; do mv "$f" `echo $f | tr ' ' '_'`; done
for f in *; do mv "$f" `echo $f | tr -d \'`; done
which renames the dir to Im_confused as intended. The file in the directory of course is not affected.
How can I replace and delete characters in subdirectories as well?
For example, for depth 2, the command is:
REP_CHARS=" →" # Characters to replace
DEL_CHARS="'," # Characters to delete
find . -maxdepth 2 | sort -r |
sed -n -e '/^\.\+$/!{p;s#.\+/#&\n#;p}' |
sed "n;n;s/[$DEL_CHARS]//g;s/[$REP_CHARS]/_/g" |
sed "n;N;s/\n//" |
xargs -L 2 -d '\n' mv 2>/dev/null
Use find with -maxdepth.
Use sort to order from the deepest.
Use sed to replace only the end part.
Use xargs to perform mv.
[Original]
├── I'm confused
│   ├── I'm confused
│   │   └── you're right
│   ├── comma, comma
│   └── you're right
└── talking heads-love → building on fire
└── talking heads-love → building on fire
[After]
├── Im_confused
│   ├── Im_confused
│   │   └── you're right
│   ├── comma_comma
│   └── youre_right
└── talking_heads-love___building_on_fire
└── talking_heads-love___building_on_fire
I would use this rename script:
#!/bin/sh
for f in *; do
g=$(printf '%s' "$f" | tr -s '[:space:]' _ | tr -d "'")
[ "$f" != "$g" ] && mv -v "$f" "$g"
done
and this find invocation
find . -depth -execdir /absolute/path/to/rename.sh '{}' +
-depth does a depth-first descent into the file hierarchy so the files are renamed before their parent directories
-execdir performs the command in the directory where the file is found, so the value of $f only contains the filename not its directory as well.
Demo
$ mkdir -p "a b/c d/e f"
$ touch a\ b/c\ d/e\ f/"I'm confused"
$ touch "a file with spaces"
$ tree
.
├── a\ b
│   └── c\ d
│   └── e\ f
│   └── I'm\ confused
├── a\ file\ with\ spaces
└── rename.sh
3 directories, 3 files
$ find . -depth -execdir /tmp/rename.sh '{}' +
renamed 'a b' -> 'a_b'
renamed 'a file with spaces' -> 'a_file_with_spaces'
renamed "I'm confused" -> 'Im_confused'
renamed 'e f' -> 'e_f'
renamed 'c d' -> 'c_d'
$ tree
.
├── a_b
│   └── c_d
│   └── e_f
│   └── Im_confused
├── a_file_with_spaces
└── rename.sh
3 directories, 3 files

Ansible getting list of files in folders containg special file

I am trying to find the the files in side folders that contain 'done' file
i was trying to do it in pure ansible but i could not make it to work, so i am trying to do it using find,ls,xargs and then running it inside ansible shell command.
example for folders and file structure:
├── a12
│   ├── 1.txt
│   ├── 2.txt
│   └── 3.txt
├── a13
│   ├── 4.txt
│   ├── 5.txt
│   ├── 6.txt
│   └── done
└── a14
├── 7.txt
├── 8.txt
├── 9.txt
└── done
and i am tring to get
4.txt
5.txt
6.txt
7.txt
8.txt
9.txt
with the command
find /tmp/test_an/ -type f -name 'done' | xargs dirname | xargs ls -1 | grep -v 'done'
I am getting
/tmp/test_an/a13:
4.txt
5.txt
6.txt
/tmp/test_an/a14:
7.txt
8.txt
9.txt
i can exclude the folder using grep, but i am looking for a cleaner\better solution
my best solution till now:
find /tmp/test_an/ -type f -name 'done' | xargs dirname | xargs -I{} find {} -type f
/tmp/test_an/a13/5.txt
/tmp/test_an/a13/4.txt
/tmp/test_an/a13/done
/tmp/test_an/a13/6.txt
/tmp/test_an/a14/8.txt
/tmp/test_an/a14/9.txt
/tmp/test_an/a14/7.txt

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" \;

How to copy all files within a directory with globstar?

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 \;

Resources