Listing all the directories that contain a specific file and store them in an array - shell

How to list all the directories that contain a specific file and store them in an array using shell script. I tried the following code but it gave me this error: ls: **/myFile.txt: No such file or directory. myFile.txt can be any file.
code:
folderArray = ($(ls **/myFile.txt | tr -d myFile.txt))
echo folderArray
for folder in ${folderArray[#]}
do
echo "myFile.txt is present in $folder"
done

You can use this command to list all the directories that contain myFile.txt:
find . -type f -name 'myFile.txt' -print0 | xargs -0 -I {} dirname {}
And to store them in an array:
arr=( $(find . -type f -name 'foo.*' -print0 | xargs -0 -I {} dirname {}) )

Use the power of zsh:
folderArray=($(echo **/myFile.txt(N^/:h)))
Flags inside () at the end are so called glob modifiers used in file names generation.
N: sets the NULL_GLOB option
^/: matches only files, not directories
:h: strips filenames from results, works as dirname

Related

Copy file into directories, and change text inside file to match index of directory

I have the following files in a directory: text.txt and text2.txt
My goal is to:
1) copy these two files into non-existing directories m06/, m07/...m20/.
2) Then, in the file text.txt, in the line containing the string mlist 06 (all the files will contain such a string), I wish to change the "06" to match the index of the directory name (for example, in m13, that line in the text.txt file would be mlist 13.
For goal 1), I got the following script which works succesfully:
#!/bin/bash
mkdir $(printf "m%02i " $(seq 6 20))
find . -maxdepth 1 -type d -name 'm[0-9][0-9]' -print0 | xargs -0 -I {} cp text.txt {}
find . -maxdepth 1 -type d -name 'm[0-9][0-9]' -print0 | xargs -0 -I {} cp text2.txt {}
For goal 2), I wish to implement a command similar to
sed -i -e '/mlist/s/06/index/' ./*/text.inp
where index would correspond to the name of the directory (i.e. index = 13 in the m13/directory).
How can I make the sed command replace 06 with the correct "index" corresponding to the name of the directory?
This would probably be easier to manage if you used loop syntax instead of one-liners:
#!/bin/sh
for i in $(seq 6 20); do
# Add a leading 0 and generate the directory name
i_z=$(printf "%02d" "$i")
dir="m${i_z}"
# Create dir
mkdir -p "$dir"
# Copy base files into dir
cp test.txt test2.txt "$dir"
# Edit the index in the files to match the dir index
sed -i -e "s/mlist.*/mlist $i_z/g" \
"${dir}/test.txt" "${dir}/test2.txt"
done

rename recursively adding parenthere if folder name ending with 4 digits in bash

I have been trying to recursively rename folders whose names ends in four digits.
For example, I have a folder name like this:
this is the name 2004
and I'm trying to rename it to:
this is the name (2004)
I've tried to split the prefix and digit parts of the name however I cannot mv as rename these folder.
Here is the code I've tried so far:
#!/bin/bash
F=$(find . -name '*[0-9]' -type d)
for i in "$F";
do
R2=$(echo "$i" | awk '{print $NF}')
R1=$(echo "$i" | sed 's/.\{4\}$//')
R3=$(echo "$R2" | sed -r "s/(^[0-9]+$)/(\1)/g")
mv "$i" "$R1 $R3"
# Even tried:
mv "\"$i"\" "\"$R2 $R3"\"
done
Does anyone can review or/and suggest some guidance to allow mv to find the initial folder and its destination?
following command:
find -name '*[0-9][0-9][0-9][0-9]' -type d -exec bash -c 'for dir; do mv "$dir" "${dir%[0-9][0-9][0-9][0-9]}(${dir#${dir%[0-9][0-9][0-9][0-9]}})"; done' - {} + -prune
should work.
double quote arround variable expansion
${dir%[0-9][0-9][0-9][0-9]} to remove last 4 digits suffix
${dir#${dir%[0-9][0-9][0-9][0-9]}} to remove previous prefix
-exec bash -c '..' - {} + the - to skip the first argument after -c command which is taken for $0, see man bash /-c
-prune at the end to prevent to search in sub tree when matched, (suppose 2004/2004 then mv 2004/2004 "2004/(2004)" or mv 2004/2004 (2004)/2004' would fail)
I found Bash annoying when it comes to find and rename files for all the escaping one needs to make. This is a cleaner Ruby solution :
#!/usr/bin/ruby
require 'fileutils'
dirs = Dir.glob('./**/*').select {|x| x =~ / [0-9]*/ }
dirs.sort().reverse().each do |dir|
new_name=dir.gsub(/(.*)( )([0-9]{4})/, '\1\2(\3)')
FileUtils.mv dir,new_name
end
When $F has more than one directoy, the for loop will consider it as a one long entry with newlines (try echo "F=[$F]").
Also use -depth, you might have topdir 2004/subdir 2004. So first rename the subdir.
When the directories don't have newlines, you can try
#!/bin/bash
while IFS= read -r orgdir; do
mv "${orgdir}" "$(sed -r "s/([0-9]+$)/(\1)/g" <<< "${orgdir}")"
done < <(find . -depth -name '*[0-9]' -type d)

Bash: execute a command on all files with extension recursively

I'm trying to use the following command:
herbalizer file_name.haml > file_name.erb
Here the file_name.haml is the file name, obviously.
How can I apply this command to all haml files in current directory recursively to all sub-directories? Filename should stay the same as mentioned above, so applying on abc.haml would be herbalizer abc.haml > abc.erb
So far: find . -type f -exec herbalizer {} \;
You're pretty close. You can use basename to strip the extension of the name of each file you find:
find . -type f -name \*.haml -exec sh -c 'herbalizer "{}" > "$(dirname {})/$(basename {} .haml).erb"' \;
I wrapped the i/o redirection in a shell command line. Filenames are enclosed in quotes in case a filename or path component contains spaces.
PS. That got you the job done, but it wasn't very elegant; so here's an alternative that uses bash's built-in substitution:
find . -type f -name \*.haml -exec bash -c 'FN="{}"; herbalizer "{}" > "${FN%.haml}.erb"' \;
You can use basename, dirname and find to get your desired results:
find . -type f -name "*.haml" | while read fname
> do
> herbalizer ${fname} > $(dirname $fname)/$(basename $fname .haml).erb
> done
Another simple method as suggested in the comments sections by #Dummy00001 is:
find . -type f -name "*.haml" | while read fname
> do
> herbalizer ${fname} > ${fname%.haml}.erb
> done
Further reference: Parameter substitution

basename and find return in unix

i have this structure in my find return:
.//r-repos/gbm/DESCRIPTION
.//r-repos/GCD/DESCRIPTION
.//ggplot2/DESCRIPTION
i want to show only parent folders of DESCRIPTION file (gbm, gcd,ggplot2) but i dont know why is "ggplot2" the only output on the screen.
r_path=$(find "$dossierpath" -iname DESCRIPTION -exec dirname {} \;)
for var in "$r_path"
do
basename "$var"
done
what i have:
output: ggplot2
what i want: output: gbm, gcd,ggplot2
Many Thanks
That's because you have decided to quote your variable: $r_path, which is the return of your find command.
If you have spaces and/or newline characters, etc in your directory names, I would recommend something like this instead:
find "$dosierpath" -iname DESCRIPTION -print0 | while IFS= read -r -d '' file; do
basename "$(dirname "$file")"
done

reducing commands with sed

I'm just interested if it's possible to reduce this commands to one line without &&?
find /backup/daily.1/var/www/ -iname "*.jpg" -type f >> ~/backuppath.txt
sed 's|/backup/daily.1||g' ~/backuppath.txt > ~/wwwpath.txt
paste -d " " ~/backuppath.txt ~/wwwpath.txt > ~/files.txt
while read line; do cp $line; done < ~/files.txt
I wouldn't write it on one line, but you can do without the intermediate files:
find /backup/daily.1/var/www/ -iname "*.jpg" -type f |
sed 's%/backup/daily.1\(.*\)%cp & \1%' |
sh -x
The sed command splits the file names into two components, the /backup/daily.1 prefix and 'the rest', and replaces that with the complete copy command copying the original name to the name without the prefix. The output of sed is fed to the shell as a script.
This should work fine unless there's a file name that contains shell metacharacters, spaces or newlines. You can improve the resiliency if there won't be newlines or single quotes in the file names with:
find /backup/daily.1/var/www/ -iname "*.jpg" -type f |
sed "s%/backup/daily.1\(.*\)%cp '&' '\1'%" |
sh -x
This wraps each filename in single quotes.
This does not deal with filenames with spaces. (This is not important, I merely
state this to preempt the inevitable comments.)
find /backup/daily.1/var/www/ -iname "*.jpg" -type f |
while read name; do cp $name ${name#/backup/daily.1}; done
You can also just do:
find /backup/daily.1/var/www/ -iname "*.jpg" \
-type f -exec sh -c 'cp "$0" "${0#/backup/daily.1}"' {} \;
which handles unusual filename well.
find /backup/daily.1/var/www/ -iname "*.jpg" -type f \
| sed 's|^/backup/daily\.1\(.*\)$|\0 \1|' \
| ( while read origin dest; do cp "$origin" "$dest"; done)
In the sed expression :
\0 is replaced by the matched string, which is the whole line is this case
\1 is replaced by the subpattern match \(.*\), that is everything from after /backup/daily.1 up to the end of the line

Resources