I want to run a bash code, that I have written, in all subfolders (where it has been "called").
The code I have written is simply named "all", and when I run it individually in each folders, it runs with no problems.
But when I run it with this code, it doesn't run (it runs, but gives errors like - files not found).
for D in *;do
all
done
I got the mistake!!! I need to enter each folder in order to run the code "all". How do I do that?
Thanks!
You do not need to run ls or find to get the list of the files in a folder. There is a pure bash solution for this. You may try something like:
for D in *; do
[ -d "$D" -a -x "$D/all" ] && "$D/all"
done
This works well even if the directory contains spaces. The ls and find ... fails in that case (if a directory is a b then D will be a and then b). Also the find also returns . dir. You can avoid this using find -maxdepth 1 -mindepth 1 -type d. The find has an advantage: it discovers hidden directories as well (.somedir). With bash this can be forced using .*, but then . and .. dirs have to be skipped.
You can use find
for D in `find . -type d`; do
"$D"/all
done
This will find every subfolder recursively! And will not follow symlinks (it's default behaviour of find)
You can specify the max depth of recursion with the parameter maaaxdepth
for D in `find . -type d -maxdepth 1`; do
"$D"/all
done
This will only take the subfolders in current pwd
EDIT by future me: Don't use that!
Better way: Globbing
for i in *
do
[[ -d $i ]] && ./"$i"/all
done
What about
for D in `ls`; do
$D/all
done
EDIT:
If you need to enter each folder:
for D in `ls`; do
cd $D
./all
cd ..
done
EDIT (spaces + directories only):
for D in `ls -d`; do
cd "$D"
./all
cd ..
done
Related
From the current directory I have multiple sub directories:
subdir1/
001myfile001A.txt
002myfile002A.txt
subdir2/
001myfile001B.txt
002myfile002B.txt
where I want to strip every character from the filenames before myfile so I end up with
subdir1/
myfile001A.txt
myfile002A.txt
subdir2/
myfile001B.txt
myfile002B.txt
I have some code to do this...
#!/bin/bash
for d in `find . -type d -maxdepth 1`; do
cd "$d"
for f in `find . "*.txt"`; do
mv "$f" "$(echo "$f" | sed -r 's/^.*myfile/myfile/')"
done
done
however the newly renamed files end up in the parent directory
i.e.
myfile001A.txt
myfile002A.txt
myfile001B.txt
myfile002B.txt
subdir1/
subdir2/
In which the sub-directories are now empty.
How do I alter my script to rename the files and keep them in their respective sub-directories? As you can see the first loop changes directory to the sub directory so not sure why the files end up getting sent up a directory...
Your script has multiple problems. In the first place, your outer find command doesn't do quite what you expect: it outputs not only each of the subdirectories, but also the search root, ., which is itself a directory. You could have discovered this by running the command manually, among other ways. You don't really need to use find for this, but supposing that you do use it, this would be better:
for d in $(find * -maxdepth 0 -type d); do
Moreover, . is the first result of your original find command, and your problems continue there. Your initial cd is without meaningful effect, because you're just changing to the same directory you're already in. The find command in the inner loop is rooted there, and descends into both subdirectories. The path information for each file you choose to rename is therefore stripped by sed, which is why the results end up in the initial working directory (./subdir1/001myfile001A.txt --> myfile001A.txt). By the time you process the subdirectories, there are no files left in them to rename.
But that's not all: the find command in your inner loop is incorrect. Because you do not specify an option before it, find interprets "*.txt" as designating a second search root, in addition to .. You presumably wanted to use -name "*.txt" to filter the find results; without it, find outputs the name of every file in the tree. Presumably you're suppressing or ignoring the error messages that result.
But supposing that your subdirectories have no subdirectories of their own, as shown, and that you aren't concerned with dotfiles, even this corrected version ...
for f in `find . -name "*.txt"`;
... is an awfully heavyweight way of saying this ...
for f in *.txt;
... or even this ...
for f in *?myfile*.txt;
... the latter of which will avoid attempts to rename any files whose names do not, in fact, change.
Furthermore, launching a sed process for each file name is pretty wasteful and expensive when you could just use bash's built-in substitution feature:
mv "$f" "${f/#*myfile/myfile}"
And you will find also that your working directory gets messed up. The working directory is a characteristic of the overall shell environment, so it does not automatically reset on each loop iteration. You'll need to handle that manually in some way. pushd / popd would do that, as would running the outer loop's body in a subshell.
Overall, this will do the trick:
#!/bin/bash
for d in $(find * -maxdepth 0 -type d); do
pushd "$d"
for f in *.txt; do
mv "$f" "${f/#*myfile/myfile}"
done
popd
done
You can do it without find and sed:
$ for f in */*.txt; do echo mv "$f" "${f/\/*myfile/\/myfile}"; done
mv subdir1/001myfile001A.txt subdir1/myfile001A.txt
mv subdir1/002myfile002A.txt subdir1/myfile002A.txt
mv subdir2/001myfile001B.txt subdir2/myfile001B.txt
mv subdir2/002myfile002B.txt subdir2/myfile002B.txt
If you remove the echo, it'll actually rename the files.
This uses shell parameter expansion to replace a slash and anything up to myfile with just a slash and myfile.
Notice that this breaks if there is more than one level of subdirectories. In that case, you could use extended pattern matching (enabled with shopt -s extglob) and the globstar shell option (shopt -s globstar):
$ for f in **/*.txt; do echo mv "$f" "${f/\/*([!\/])myfile/\/myfile}"; done
mv subdir1/001myfile001A.txt subdir1/myfile001A.txt
mv subdir1/002myfile002A.txt subdir1/myfile002A.txt
mv subdir1/subdir3/001myfile001A.txt subdir1/subdir3/myfile001A.txt
mv subdir1/subdir3/002myfile002A.txt subdir1/subdir3/myfile002A.txt
mv subdir2/001myfile001B.txt subdir2/myfile001B.txt
mv subdir2/002myfile002B.txt subdir2/myfile002B.txt
This uses the *([!\/]) pattern ("zero or more characters that are not a forward slash"). The slash has to be escaped in the bracket expression because we're still inside of the pattern part of the ${parameter/pattern/string} expansion.
Maybe you want to use the following command instead:
rename 's#(.*/).*(myfile.*)#$1$2#' subdir*/*
You can use rename -n ... to check the outcome without actually renaming anything.
Regarding your actual question:
The find command from the outer loop returns 3 (!) directories:
.
./subdir1
./subdir2
The unwanted . is the reason why all files end up in the parent directory (that is .). You can exclude . by using the option -mindepth 1.
Unfortunately, this was onyl the reason for the files landing in the wrong place, but not the only problem. Since you already accepted one of the answers, there is no need to list them all.
a slight modification should fix your problem:
#!/bin/bash
for f in `find . -maxdepth 2 -name "*.txt"`; do
mv "$f" "$(echo "$f" | sed -r 's,[^/]+(myfile),\1,')"
done
note: this sed uses , instead of / as the delimiter.
however, there are much faster ways.
here is with the rename utility, available or easily installed wherever there is bash and perl:
find . -maxdepth 2 -name "*.txt" | rename 's,[^/]+(myfile),/$1,'
here are tests on 1000 files:
for `find`; do mv 9.176s
rename 0.099s
that's 100x as fast.
John Bollinger's accepted answer is twice as fast as the OPs, but 50x as slow as this rename solution:
for|for|mv "$f" "${f//}" 4.316s
also, it won't work if there is a directory with too many items for a shell glob. likewise any answers that use for f in *.txt or for f in */*.txt or find * or rename ... subdir*/*. answers that begin with find ., on the other hand, will also work on directories with any number of items.
How would I go about search if the folder "test" is anywhere within my current dir and once the first occurrence is found automatically cd into it? Using the terminal on mac (bash).
You can write:
cd "$(find . -type d -name test -print -quit)"
(Caveat: this works for test, but will not work for any filename ending in newlines. Fortunately, I've never heard of anyone having a real filename that ended in a newline — it's possible, but never done — and the filename is an argument under your control. So I can't imagine that this will be a problem.)
The below will work if that dir exists ->
cd `find . -name test -type d`
For bash I guess below should work ->
cd $(find . -name test -type d)
You could use the globstar option of bash which searches directories recursively.
shopt -s globstar
for i in **/*test; do
if [[ -d $i ]]; then
cd "$i"
break
fi
done
You can try :
if [ -d "test" ]; then cd test; fi
Or simply :
[ -d "test" ] && cd test
Or just do ...
cd test
... if test is a directory the you just cd'ed into it.
If is not ... so what.
I am very new to bash so please don't overcomplicate the answer!
I have roughly 200 sub-directories each named similarly to this. (I think they are sub directories. They live within another directory at least.)
XMMXCS J083454.8+553420.58
I need to bulk rename all of these directories and change the '+' in the directory name to '-'.
To change the names of my directory I have tried:
find . -depth -type d -name + -exec sh -c 'mv "${0}" "${0%/+}/-"' {} \;
and
find . -name + -type d -execdir mv {} - \
However I think this isn't working because + and - aren't letter characters.
How do I get around this?
Everything I have found online relates to renaming files as opposed to directories, and if anyone knows how to get round this without having to rename them all manually it would be very appreciated.
This previous question I have tried and the syntax doesn't work for me. The folders are all called the same thing after running.
Rename multiple directories matching pattern
Thanks
You can have a script like this.
#!/bin/bash
DIR='.' ## Change to the directory you want.
for SDIR in "$DIR"/*; do
[[ -d $SDIR ]] || continue ## Skip if it's not a directory
BASE=${SDIR##*/} ## Gets the base filename (removes directory part)
NEW_NAME=${BASE//+/-} ## Creates a new name based from $BASE with + chars changed to -
echo mv -- "$SDIR" "$DIR/$NEW_NAME" ## Rename. Remove echo if you think it works the right way already.
done
Then run bash script.sh.
Your original syntax was pretty close, try something like this
find -mindepth 1 -maxdepth 1 -type d -name '*+*' -exec bash -c 'mv "${0}" "${0//+/-}"' {} \;
Issues
-depth Performs a dfs traversal, but it seems like you only want directories one level deep
You need to match globs that contain +. So *+* and not just + (quoting is needed with globs so they get processed by find and not the shell)
With "${0%/+}/-" you seem to be mixing up a few syntaxes, ${0//SUBSTRING/TO_REPLACE} with replace all instances of SUBSTRING with TO_REPLACE
I have a directory of 24 sub-directories, no chronological order
I need to enter a sub directory, unzip a file there and then call "tophat" command on the unzipped file, then move to the next sub-directory. the loop should go over all the sub-directories with these commands.
I don't really know how to create this loop (I need it to run on a display and not according to numeric order)
(for sure many of you who work with RNA-seq results are familiar with this issue)
If anyone can help me with it
I'll be very thankful
for d in "/path/to/"*/
do
cd "$d" || continue
unzip the_file.zip
tophat the_file
done
Use find:
find /path -type d -print | \
while read path ; do
...
done
Note: This loop breaks when the file names contain new line characters.
Same using for loop
for directory in `find /path -type d -print`
do
cd "$directory"
unzip zip_filename
sh zip_dir/script_file.sh &
done
My directory structure is as follows
Directory1\file1.jpg
\file2.jpg
\file3.jpg
Directory2\anotherfile1.jpg
\anotherfile2.jpg
\anotherfile3.jpg
Directory3\yetanotherfile1.jpg
\yetanotherfile2.jpg
\yetanotherfile3.jpg
I'm trying to use the command line in a bash shell on ubuntu to take the first file from each directory and rename it to the directory name and move it up one level so it sits alongside the directory.
In the above example:
file1.jpg would be renamed to Directory1.jpg and placed alongside the folder Directory1
anotherfile1.jpg would be renamed to Directory2.jpg and placed alongside the folder Directory2
yetanotherfile1.jpg would be renamed to Directory3.jpg and placed alongside the folder Directory3
I've tried using:
find . -name "*.jpg"
but it does not list the files in sequential order (I need the first file).
This line:
find . -name "*.jpg" -type f -exec ls "{}" +;
lists the files in the correct order but how do I pick just the first file in each directory and move it up one level?
Any help would be appreciated!
Edit: When I refer to the first file what I mean is each jpg is numbered from 0 to however many files in that folder - for example: file1, file2...... file34, file35 etc... Another thing to mention is the format of the files is random, so the numbering might start at 0 or 1a or 1b etc...
You can go inside each dir and run:
$ mv `ls | head -n 1` ..
If first means whatever the shell glob finds first (lexical, but probably affected by LC_COLLATE), then this should work:
for dir in */; do
for file in "$dir"*.jpg; do
echo mv "$file" "${file%/*}.jpg" # If it does what you want, remove the echo
break 1
done
done
Proof of concept:
$ mkdir dir{1,2,3} && touch dir{1,2,3}/file{1,2,3}.jpg
$ for dir in */; do for file in "$dir"*.jpg; do echo mv "$file" "${file%/*}.jpg"; break 1; done; done
mv dir1/file1.jpg dir1.jpg
mv dir2/file1.jpg dir2.jpg
mv dir3/file1.jpg dir3.jpg
Look for all first level directories, identify first file in this directory and then move it one level up
find . -type d \! -name . -prune | while read d; do
f=$(ls $d | head -1)
mv $d/$f .
done
Building on the top answer, here is a general use bash function that simply returns the first path that resolves to a file within the given directory:
getFirstFile() {
for dir in "$1"; do
for file in "$dir"*; do
if [ -f "$file" ]; then
echo "$file"
break 1
fi
done
done
}
Usage:
# don't forget the trailing slash
getFirstFile ~/documents/
NOTE: it will silently return nothing if you pass it an invalid path.