Linux find files and folders based on name length but output full path - linux

I have the following folder structure:
├── longdirectorywithsillylengththatyouwouldntnormallyhave
│   ├── asdasdads9ads9asd9asd89asdh9asd9asdh9asd
│   └── sinlf
└── shrtdir
├── nowthisisalongfile0000000000000000000000000
└── sfile
I need to find files and folders where their names length is longer is than x characters. I have been able to achieve this with:
find . -exec basename '{}' ';' | egrep '^.{20,}$'
longdirectorywithsillylengththatyouwouldntnormallyhave
asdasdads9ads9asd9asd89asdh9asd9asdh9asd
nowthisisalongfile0000000000000000000000000
However, This only outputs the name of the file or folder in question. How can I output the full path of resulting matches like this:
/home/user/Desktop/longdirectorywithsillylengththatyouwouldntnormallyhave
/home/user/Desktop/longdirectorywithsillylengththatyouwouldntnormallyhave/asdasdads9ads9asd9asd89asdh9asd9asdh9asd
/home/user/Desktop/shrtdir/nowthisisalongfile0000000000000000000000000

If you use basename on your files, you lose the information about what file you are actually handling.
Therefore you have to change your regex to be able to recognize the length of the last path component.
The simplest way I could think of, would be:
find . | egrep '[^/]{20,}$' | xargs readlink -f
This makes use of the fact, that filenames cannot contain slashes.
As the result then contains path relative to you current cwd, readlink to can be used to give you the full path.

I cant test it right now but this should do the job:
find $(pwd) -exec basename '{}' ';' | egrep '^.{20,}$'

find -name "????????????????????*" -printf "$PWD/%P\n"
The -printf option of find is very mighty. %P:
%P File's name with the name of the starting-point under which it was found removed. (%p starts with ./).
So we add $PWD/ in front.
/home/stefan/proj/mini/forum/tmp/Mo/shrtdir/nowthisisalongfile0000000000000000000000000
/home/stefan/proj/mini/forum/tmp/Mo/longdirectorywithsillylengththatyouwouldntnormallyhave
/home/stefan/proj/mini/forum/tmp/Mo/longdirectorywithsillylengththatyouwouldntnormallyhave/asdasdads9ads9asd9asd89asdh9asd9asdh9asd
To prevent us from manually counting question marks, we use:
for i in {1..20}; do echo -n "?" ; done; echo
????????????????????

Related

find files based on extension but display name without extension no basename, sed, awk, grep or ; allowed

I need to write a script that lists all the files with a .gif extension in the current directory and all its sub-directories BUT DO NOT use ANY of:
basename
grep
egrep
fgrep
rgrep
&&
||
;
sed
awk
AND still include hidden files.
I tried find . -type f -name '*.gif' -printf '%f\n' which will succesfully display .gif files, but still shows extension. Here's the catch: if I try to use cut -d . -f 1 to remove file extension, I also remove hidden files (which I don't want to) because their names start with ".".
Then I turned to use tr -d '.gif' but some of the files have a 'g' or a '.' in their name.
I also tried to use some of these answers BUT all of them include either basename, sed, awk or use some ";" in their script.
With so many restrictions I really don't know if it's even possible to achieve that but I'm supposed to.
How would you do it?
files/dirs structure:
$ tree -a
.
├── bar
├── bar.gif
├── base
│   └── foo.gif
├── foo
│   └── aaa.gif
└── .qux.gif
3 directories, 4 files
Code
find -type f -name '*.gif' -exec bash -c 'printf "%s\n" "${#%.gif}"' bash {} +
Output
./bar
./.qux
./foo/aaa
./base/foo
Explanations
Parameter Expansion expands parameters: $foo, $1. You can use it to perform string or array operations: "${file%.mp3}", "${0##*/}", "${files[#]: -4}". They should always be quoted. See: http://mywiki.wooledge.org/BashFAQ/073 and "Parameter Expansion" in man bash. Also see http://wiki.bash-hackers.org/syntax/pe.
Something like:
find . -name '*.gif' -type f -execdir bash -c 'printf "%s\n" "${#%.*}"' bash {} +
Using perl:
perl -MFile::Find::Rule -E '
say s/\.gif$//r for File::Find::Rule
->file()
->name(qr/\.gif\z/)
->in(".")
'
Output:
bar
.qux
foo/aaa
base/foo

How to list directories and files in a Bash by script?

I would like to list directory tree, but I have to write script for it and as parameter script should take path to base directory. Listing should start from this base directory.
The output should look like this:
Directory: ./a
File: ./a/A
Directory: ./a/aa
File: ./a/aa/AA
Directory: ./a/ab
File: ./a/ab/AB
So I need to print path from the base directory for every directory and file in this base directory.
UPDATED
Running the script I should type in the terminal this: ".\test.sh /home/usr/Desktop/myDirectory" or ".\test.sh myDirectory" - since I run the test.sh from the Desktop level.
And right now the script should be run from the level of /home/usr/Dekstop/myDirectory"
I have the following command in my test.sh file:
find . | sed -e "s/[^-][^\/]*\// |/g"
But It is the command, not shell code and prints the output like this:
DIR: dir1
DIR: dir2
fileA
DIR: dir3
fileC
fileB
How to print the path from base directory for every dir or file from the base dir? Could someone help me to work it out?
Not clear what you want maybe,
find . -type d -printf 'Directory: %p\n' -o -type f -printf 'File: %p\n'
However to see the subtree of a directory, I find more useful
find "$dirname" -type f
To answer comment it can also be done in pure bash (builtin without external commands), using a recursive function.
rec_find() {
local f
for f in "$1"/*; do
[[ -d $f ]] && echo "Directory: $f" && rec_find "$f"
[[ -f $f ]] && echo "File: $f"
done
}
rec_find "$1"
You can use tree command. Key -L means max depth. Examples:
tree
.
├── 1
│   └── test
├── 2
│   └── test
└── 3
└── test
3 directories, 3 files
Or
tree -L 1
.
├── 1
├── 2
└── 3
3 directories, 0 files
Create your test.sh with the below codes. Here you are reading command line parameter in system variable $1 and provides parameter to find command.
#!/bin/bash #in which shell you want to execute this script
find $1 | sed -e "s/[^-][^\/]*\// |/g"
Now how will it work:-
./test.sh /home/usr/Dekstop/myDirectory #you execute this command
Here command line parameter will be assign into $1. More than one parameter you can use $1 till $9 and after that you have to use shift command. (You will get more detail information online).
So your command will be now:-
#!/bin/bash #in which shell you want to execute this script
find /home/usr/Dekstop/myDirectory | sed -e "s/[^-][^\/]*\// |/g"
Hope this will help you.

Creating empty file in all subfolders with unique name - bash

Similar to this question
but I want to have file name be the same name as the directory with "_info.txt" appended to it.
I have tried this
#!/bin/bash
find . -mindepth 1 -maxdepth 1 -type d | while read line; do
touch /Users/xxx/git/xxx/$line/$line_info.txt
done
It is creating ".txt" in each subdirectory.
What am I missing ?
The crucial mistake is that the underscore character is a valid character for a bash variable name:
$ a=1
$ a_b=2
$ echo $a_b
2
There are several ways around this:
$ echo "$a"_b
1_b
$ echo $a'_b'
1_b
$ echo ${a}_b
1_b
As for your task, here's a fast way:
find . -mindepth 1 -maxdepth 1 -type d -printf "%p_info.txt\0" | xargs -0 touch
The find -printf prints the path of each directory, and printf's %p is unaffected by an underscore after it. Then, xargs passes the filenames as many arguments to few runs of touch, making the creation of the files much faster.
Figured it out:
#!/bin/bash
find . -mindepth 1 -maxdepth 1 -type d | while read line; do
touch /Users/xxxx/git/xxxx/$line/"$line"_info.txt
done
I think I can still do the "find ." better as I have to run in directory I want the files added to. Ideally it should be able to be run anywhere.
If you are not recursing at all, you don't need find. To loop over subdirectories, all you need is
for dir in */; do
touch /Users/xxxx/git/xxxx/"$dir/${dir%/}_info.txt"
done
Incidentally, notice that the variable needs to be in double quotes always.
Try this script :
#!/bin/bash
find $1 -type d | while read line
do
touch "$line/$(basename $( readlink -m $line))_info.txt"
done
Save it as, say, appendinfo and run it as
./appendinfo directory_name_which_include_symlinks
Before
ssam#udistro:~/Documents/so$ tree 36973628
36973628
`-- 36973628p1
`-- 36973628p2
`-- 36973628p3
3 directories, 0 files
After
ssam#udistro:~/Documents/so$ tree 36973628
36973628
|-- 36973628_info.txt
`-- 36973628p1
|-- 36973628p1_info.txt
`-- 36973628p2
|-- 36973628p2_info.txt
`-- 36973628p3
`-- 36973628p3_info.txt
3 directories, 4 files
Note : readlink canonicalizes the path by following the symlinks. It is not required if, say, you're not going to give arguments like ..
Try this :
find . /Users/xxx/git/xxx/ -mindepth 1 -maxdepth 1 -type d -exec bash -c 'touch ${0}/${0##*/}_info.txt' {} \;
You're missing the path to the folder. I think you meant to write
touch "./$folder/*/*_info.txt"
Although this still only works if you start in the right directory. You should really have written something like
touch "/foo/bar/user/$folder/*/*_info.txt"
However, your use of "*/*_info.txt" is very... questionable... to say the least. I'm no bash expert, but I don't expect it to work. Reading everything in a directory (using "find" for example, and only accepting directories) and piping that output to "touch" would be a better approach.

Bash script to rename file names with correct date format in all sub folders in Linux

I have a buch of logs with names in "filename.logdate month year" (for example, filename.log25 Aug 2015, note there are space between the date/month/year) and I'd like to change them to "filename.logmonthdateyear" (for example filename.logOct052015, with no space).
These files are in a bunch of sub folders which makes it more challenging.
Parent Folder
--- sub folder1
file1
file2
--- sub folder2
file3
file4
etc.
Can anyone suggest a bash script that can do this?
Thank you!
find and rename should do the trick
strawman example:
to go from
...
├── foo/
│   ├── file name with spaces
│   └── bar/
│   └── another file with spaces
...
you can use
find foo/ -type f -exec rename 's/ //g' '{}' \;
to get
...
├── foo/
│ ├── filenamewithspaces
│ └── bar/
│ └── anotherfilewithspaces
...
in your case:
in your case, it would be something like
find path/to/files/ -type f -exec rename 's/ //g' '{}' \;
but you can use fancier filters in your find command like
find path/to/files/ -type f -name *.log* -exec rename 's/ //g' '{}' \;
to select only .log files in case there are other file names with spaces you don't want to touch
heads up:
as pointed out in the comments there's the potential to overwrite files if their names only differ by space placement (e.g., a bc.log and ab c.log if carelessly renamed would end up with a single abc.log).
for your case, you have two things on your side:
rename will give you a heads up as long as you're not using it's --force option
and will give you a helpful message like ./ab c.log not renamed: ./abc.log already exists
your files are named programatically, and you're stripping the spaces in dates, so, assuming that's all you have in there, you shouldn't have any problems
regardless, it's good to be mindful of this sort of thing
This is a way to do it with just Bash (4+) and 'mv':
# Prevent breakages when nothing matches patterns
shopt -s nullglob
# Enable '**' matches (requires Bash 4)
shopt -s globstar
topdir=$PWD
for folder in **/ ; do
# Work in the directory to avoid problems if its path has spaces
cd -- "$folder"
for file in *' '*' '* ; do
# Use the '-i' option to prevent silent clobbering
mv -i -- "$file" "${file// /}"
done
cd -- "$topdir"
done
If there is just one level of subfolders (as stated in the question), the requirement for Bash 4+ can be dropped: remove the shopts -s globstar, and change the first line of the outer loop to for folder in */ ; do.

How to use 'mv' command to move files except those in a specific directory?

I am wondering - how can I move all the files in a directory except those files in a specific directory (as 'mv' does not have a '--exclude' option)?
Lets's assume the dir structure is like,
|parent
|--child1
|--child2
|--grandChild1
|--grandChild2
|--grandChild3
|--grandChild4
|--grandChild5
|--grandChild6
And we need to move files so that it would appear like,
|parent
|--child1
| |--grandChild1
| |--grandChild2
| |--grandChild3
| |--grandChild4
| |--grandChild5
| |--grandChild6
|--child2
In this case, you need to exclude two directories child1 and child2, and move rest of the directories in to child1 directory.
use,
mv !(child1|child2) child1
This will move all of rest of the directories into child1 directory.
Since find does have an exclude option, use find + xargs + mv:
find /source/directory -name ignore-directory-name -prune -print0 | xargs -0 mv --target-directory=/target/directory
Note that this is almost copied from the find man page (I think using mv --target-directory is better than cpio).
First get the names of files and folders and exclude whichever you want:
ls --ignore=file1 --ignore==folder1 --ignore==regular-expression1 ...
Then pass filtered names to mv as the first parameter and the second parameter will be the destination:
mv $(ls --ignore=file1 --ignore==folder1 --ignore==regular-expression1 ...) destination/
This isn't exactly what you asked for, but it might do the job:
mv the-folder-you-want-to-exclude somewhere-outside-of-the-main-tree
mv the-tree where-you-want-it
mv the-excluded-folder original-location
(Essentially, move the excluded folder out of the larger tree to be moved.)
So, if I have a/ and I want to exclude a/b/c/*:
mv a/b/c ../c
mv a final_destination
mkdir -p a/b
mv ../c a/b/c
Or something like that. Otherwise, you might be able to get find to help you.
This will move all files at or below the current directory not in the ./exclude/ directory to /wherever...
find -E . -not -type d -and -not -regex '\./exclude/.*' -exec echo mv {} /wherever \;
ls | grep -v exclude-dir | xargs -t -I '{}' mv {} exclude-dir
rename your directory to make it hidden so the wildcard does not see it:
mv specific_dir .specific_dir
mv * ../other_dir
#!/bin/bash
touch apple banana carrot dog cherry
mkdir fruit
F="apple banana carrot dog cherry"
mv ${F/dog/} fruit
# this removes 'dog' from the list F, so it remains in the
current directory and not moved to 'fruit'
Inspired by #user13747357 's answer.
First you can ls the file and filter them by:
ls | egrep -v '(dir_name|file_name.ext)'
Then you can run the following command to move the files except the specific ones:
mv $(ls | egrep -v '(dir_name|file_name.ext)') target_dir
* Note that I tested this inside a specific directory. Cross-directory operation should be more carefully executed :)
suppose you directory is
.
├── dir1
│ └── a.txt
├── dir2
│ ├── b.txt
│ └── hello.c
├── file1.txt
├── file2.txt
└── file3.txt
and you gonna put file1 file2 file3 into dir2.
you can use
mv $(ls -p | grep -v /) /dir2 to finish it, because
ls -p | grep -v / will print all files except directory in cwd.
For example, if I want to move all files/directories - except a specified file or directory - inside "var/www/html" to a sub-folder named "my_sub_domain", then I use "mv" with the command "!(what_to_exclude)":
$ cd /var/www/html
$ mv !(my_sub_domain) my_sub_domain
To exclude more I use "|" to seperate file/directory names:
$ mv !(my_sub_domain|test1.html) my_sub_domain
mv * exclude-dir
was the perfect solution for me

Resources