how do i return all the paths after set point using bash - bash

i am returning this:
./nas/cdn/catalog/swatches
./nas/cdn/catalog/product_shots
./nas/cdn/catalog/product_shots/high_res
./nas/cdn/catalog/product_shots/high_res/back
./nas/cdn/catalog/product_shots/high_res/front
./nas/cdn/catalog/product_shots/low_res
./nas/cdn/catalog/product_shots/low_res/back
./nas/cdn/catalog/product_shots/low_res/front
./nas/cdn/catalog/product_shots/thumbs
./nas/cdn/catalog/full_length
./nas/cdn/catalog/full_length/high_res
./nas/cdn/catalog/full_length/low_res
./nas/cdn/catalog/cropped
./nas/cdn/catalog/drawings
what is the correct way to remove ./nas/cdn/catalog/ from this?
this is the code i have, so far
BASE='./nas/cdn/catalog'
echo $BASE
for d in $(find . -type d -regex "${BASE}/[^.]*")
do
echo $(basename $d)
done
bit this just returns the last folder, i like to return /swatches, /product_shots/high_res etc...

Use sed like below,
BASE='./nas/cdn/catalog'
echo $BASE
for d in $(find . -type d -regex "${BASE}/[^.]*")
do
sed 's~^\([^/]*/\)\{4\}~~' <<< "$d"
done
Example:
$ var="./nas/cdn/catalog/drawings"
$ sed 's~^\([^/]*/\)\{4\}~~' <<< "$var"
drawings

A somewhat simpler approach:
BASE='./nas/cdn/catalog'
echo "$BASE"
( cd "$BASE" ; find */ -type d )
Note: this is not perfectly robust; it will fail when any of the directories immediately inside in $BASE starts with a hyphen. It should only be used when you can guarantee that that is not the case.

Related

Loop over find result in bash

I have a bash script written by some previous colleague in my company. It's shellcheck result is horrible and me, who is using zsh can't run the script. He seems to use the notorious find with for loop thingy in bash. But I can't figure out how to get it better.
At the moment i got a temporary fix.
this is his code
#!/bin/bash
releases=$(for d in $(find ${DELIVERIES} -maxdepth 1 -type d -name "*_delivery_33_SR*" | sort) ; do echo ${d##*_} ; done)
for sr in ${releases[#]}
do
echo "Release $sr"
deliveries=$(find ${deliveries_path}/*${sr}/ -type f -name "*.ear" -o -name "*.war" | sort)
if [ ! -e ${sr}.txt ]
then
for d in ${deliveries[#]}
do
echo "$(basename $d)" | tee -a ${sr}.txt
done
fi
echo
done
And this is my code that get to even loop the first part.
#!/bin/bash
for release in $(for d in $(find "${DELIVERIES}" -maxdepth 1 -type d -name "*_delivery_33_SR*" | sort) ; do echo "${d##*_}" ; done)
do
echo "Release $release"
done
As you can see I needed to put the find inside the loop and I cant save it in an variable, because when i try to loop over it will try to put \n everywhere and it is like a single element? Could any1 suggest How should I solve this problem, because this previous colleague uses this kind of find search a lot.
EDIT:
The script went to each folder with a specific name and then created a file X.X.X.txt with the version number in the X part. And appended the filenames inside the subfolder to the X.X.X.txt
Blindly refactoring gets me something like
#!/bin/bash
for d in "$DELIVERIES"/*_delivery_33_SR*/; do
sr=${d##*_}
echo "Release $sr"
if [ ! -e "${sr}.txt" ]
then
find "${deliveries_path}"/*"${sr}"/ -type f -name "*.ear" -o -name "*.war" |
sort |
xargs -n 1 basename |
tee -a "$sr.txt"
fi
echo
done

How to stop Bash expansion of '*.h" in a function?

In trying to run the following function—Bash is expanding my variable in an unexpected way—thus preventing me from getting my expected result.
It comes down to the way bash deals with a "*.h" which I am passing in to the function.
Here is the function I call:
link_files_of_type_from_directory "*.h" ./..
And where I would expect this variable to stay this way all the way through at some point, by the time it hits the echo $command_to_run; part of my Bash script...this variable has expanded to...
MyHeader1.h MyHeader2.h MyHeader3.h
and so on.
What I want is for Bash to not expand my files so that my code runs the following:
find ./.. -type f -name '*.h'
Instead of
find ./.. -type f -name MyHeader1.h MyHeader2.h MyHeader3.h
This is the code:
function link_files_of_type_from_directory {
local file_type=$1;
local directory_to_link=$2;
echo "File type $file_type";
echo "Directory to link $directory_to_link";
command="find $directory_to_link -type f -name $file_type";
echo $command;
#for i in $(find $directory_to_link -type f -name $file_type);
for i in $command;
do
echo $i;
if test -e $(basename $i); then
echo $i exists;
else
echo Linking: $i;
ln -s $i;
fi
done;
}
How can I prevent the expansion so that Bash does search for files that end in *.h in my the directory I want to pass in?
UPDATE 1:
So I've updated the call to be
link_files_of_type_from_directory "'*.h'" ..
And the function now assembles the string of the command to be evaluated like so:
mmd="find $directory_to_link -type f -name $file_type";
When I echo it out—it's correct :)
find .. -type f -name '*.h'
But I can't seem to get the find command to actually run. Here are the errors / mistakes I'm getting while trying to correctly assemble the for loop:
# for i in $mmd; # LOOPS THROUGH STRINGS IN COMMAND
# for i in '$(mdd)'; # RUNS MMD LITERALLY
# for i in ${!mmd}; # Errors out with: INVALID VARIABLE NAME — find .. -type f -name '*.h':
Would love help on this part—even though it is a different question :)
With quoting of your variables, removed semicolons and your loop wrapped into an -exec action to prevent problems with spaces, tabs and newlines in filenames, your function looks like this:
function link_files_of_type_from_directory {
local file_type=$1
local directory_to_link=$2
echo "File type $file_type"
echo "Directory to link $directory_to_link"
find "$directory_to_link" -type f -name "$file_type" -exec sh -c '
for i do
echo "$i"
if test -e "$(basename "$i")"; then
echo "$i exists"
else
echo "Linking: $i"
ln -s "$i"
fi
done
' sh {} +
}

Find those files with an underscore that have no matching file without underscore in shell?

I have got files like klein_parat-strong-aqua-stop.jpg. Most of them have a counterpart with all underscores replaced with a minus, klein-parat-strong-aqua-stop.jpg for example.
Some of them however do not have that counterpart. I want to find those files in a folder including its subfolders. How would I do that?
THanks!
Try this two commands
for FF in `find . -type f -name "*_*" `;do echo $FF;done > list
for II in `<list`;do I=`basename $II`;F2=`echo $I | tr '_' '-'`;FOUND=`find . -name "$F2"`;[ "$FOUND" == "" ] && { echo $II; };done
The basic idea would be find all files first and then test the existence of the counterpart, as mentioned in the comment:
for i in `find . -name '*_*'`
do
test -f "${i//_/-}" || echo "$i"
done
A one-liner:
for i in `find . -name '*_*'`; do test -f "${i//_/-}" || echo "$i"; done
test -f file: check whether file is a normal file or not.

Why does this conditional return "No such file or directory"

My conditional works properly when the dirs exist, but if they don't, it seems to execute both then and else statements (is that the correct term?).
script.sh
#!/bin/bash
if [[ $(find path/to/dir/*[^thisdir] -type d -maxdepth 0) ]]
then
find path/to/dir/*[^thisdir] -type d -maxdepth 0 -exec mv {} new/location \;
echo "Huzzah!"
else
echo "hey hey hey"
fi
prompt
For the first call, the dirs are there; in the second, they've been moved from the first call.
$ sh script.sh
Huzzah!
$ sh script.sh
find: path/to/dir/*[^thisdir]: No such file or directory
hey hey hey
How can I fix this?
tried suggestion(s)
if [[ -d $(path/to/dir/*[^thisdir]) ]]
then
find path/to/dir/*[^thisdir] -type d -maxdepth 0 -exec mv {} statamic-1.3-personal/admin/themes \;
echo "Huzzah!"
else
echo "hey hey hey"
fi
result
$ sh script.sh
script.sh: line 1: path/to/dir/one_of_the_dirs_to_be_moved: is a directory
hey hey hey
There seem to be some errors:
First, the pattern path/to/dir/*[^thisdir] is interpreted in bash in the same manner than path/to/dir/*[^dihstr] mean *all filename ending by d, i, h, s, t or r.
Than if you are searching for something in this dir (path/to/dir) but not on path/to/dir/thisdir, and not on a nth subdir, you could bannish find and write:
Edit: There was an error on my sample too: [ -e $var ] was wrong.
declare -a files=( path/to/dir/!(thisdir) )
if [ -e $files ] ;then
mv -t newlocation "${files[#]}"
echo "Huzzah!"
else
echo "hey hey hey"
fi
If you need find for searching in subirs, please give us samples and/or more descriptions.
Your error is probably occurring at if [[ $(find path/to/dir/*[^thisdir] -type d -maxdepth 0) ]] and then it goes to else because find errors out.
find wants its directory parameter to exist. Based on what you are trying to do you should probably consider
$(find path/to/dir/ -name "appropriate name pattern" -type d -maxdepth 1)
Also, I'd consider using actual logical function in if. See this for file conditionals.
Try adding a #!/bin/bash on the first line to ensure that it is bash that is executing your script, as recommended by this post:
Why is both the if and else executed?
The OP wishes to move all files excluding thisdir to a new location.
A solution using find would be to exclude thisdir using find's functionality, rather than by using bash's shell expansion:
#!/bin/bash
if [[ $(find path/to/directory/* -maxdepth 0 -type d -not -name 'thisdir') ]]
then
find path/to/directory/* -maxdepth 0 -type d -not -name 'thisdir' -exec mv {} new/location \;
echo "Huzzah!"
else
echo "hey hey hey"
fi
This has been tested, and works under bash version 4.2.39, and GNU findutils v4.5.10.

Perform an action in every sub-directory using Bash

I am working on a script that needs to perform an action in every sub-directory of a specific folder.
What is the most efficient way to write that?
A version that avoids creating a sub-process:
for D in *; do
if [ -d "${D}" ]; then
echo "${D}" # your processing here
fi
done
Or, if your action is a single command, this is more concise:
for D in *; do [ -d "${D}" ] && my_command; done
Or an even more concise version (thanks #enzotib). Note that in this version each value of D will have a trailing slash:
for D in */; do my_command; done
for D in `find . -type d`
do
//Do whatever you need with D
done
The simplest non recursive way is:
for d in */; do
echo "$d"
done
The / at the end tells, use directories only.
There is no need for
find
awk
...
Use find command.
In GNU find, you can use -execdir parameter:
find . -type d -execdir realpath "{}" ';'
or by using -exec parameter:
find . -type d -exec sh -c 'cd -P "$0" && pwd -P' {} \;
or with xargs command:
find . -type d -print0 | xargs -0 -L1 sh -c 'cd "$0" && pwd && echo Do stuff'
Or using for loop:
for d in */; { echo "$d"; }
For recursivity try extended globbing (**/) instead (enable by: shopt -s extglob).
For more examples, see: How to go to each directory and execute a command? at SO
Handy one-liners
for D in *; do echo "$D"; done
for D in *; do find "$D" -type d; done ### Option A
find * -type d ### Option B
Option A is correct for folders with spaces in between. Also, generally faster since it doesn't print each word in a folder name as a separate entity.
# Option A
$ time for D in ./big_dir/*; do find "$D" -type d > /dev/null; done
real 0m0.327s
user 0m0.084s
sys 0m0.236s
# Option B
$ time for D in `find ./big_dir/* -type d`; do echo "$D" > /dev/null; done
real 0m0.787s
user 0m0.484s
sys 0m0.308s
find . -type d -print0 | xargs -0 -n 1 my_command
This will create a subshell (which means that variable values will be lost when the while loop exits):
find . -type d | while read -r dir
do
something
done
This won't:
while read -r dir
do
something
done < <(find . -type d)
Either one will work if there are spaces in directory names.
You could try:
#!/bin/bash
### $1 == the first args to this script
### usage: script.sh /path/to/dir/
for f in `find . -maxdepth 1 -mindepth 1 -type d`; do
cd "$f"
<your job here>
done
or similar...
Explanation:
find . -maxdepth 1 -mindepth 1 -type d :
Only find directories with a maximum recursive depth of 1 (only the subdirectories of $1) and minimum depth of 1 (excludes current folder .)
the accepted answer will break on white spaces if the directory names have them, and the preferred syntax is $() for bash/ksh. Use GNU find -exec option with +; eg
find .... -exec mycommand +; #this is same as passing to xargs
or use a while loop
find .... | while read -r D
do
# use variable `D` or whatever variable name you defined instead here
done
if you want to perform an action INSIDE the folder and not ON folder.
Explanation: You have many pdfs and you would like to concetrate them inside a single folder.
my folders
AV 001/
AV 002/
for D in *; do cd "$D"; # VERY
DANGEROUS COMMAND - DONT USE
#-- missing "", it will list files too. It can go up too.
for d in */; do cd "$d"; echo $d; cd ..; done; # works
succesfully
for D in "$(ls -d */)"; do cd "$D"; done; #
bash: cd: $'Athens Voice 001/\nAthens Voice 002/' - there is no such
folder
for D in "$(*/)"; do cd "$D"; done; # bash: Athens
Voice 001/: is folder
for D in "$(`find . -type d`)"; do cd $D; done; # bash: ./Athens: there is no such folder or file
for D in *; do if [ -d "${D}" ] then cd ${D}; done; # many
arguments

Resources