Copy specific files from different directories without changing file extension - bash

I have a bunch of image files named as 1.jpg in several directories I want to copy that into another.
I tried using the below command (consider 100 folders)
find folder/{1..100} -type f -name '1.*' -exec cp --backup=numbered "{}" folder/new/ \;
but the file extension get changed as below
1.jpg.~1~
1.jpg.~2~
1.jpg.~3~
...
I am expecting my output should look something like below
1.~1~.jpg
1.~2~.jpg
1.~3~.jpg
...
(or)
1(1).jpg
1(2).jpg
1(3).jpg
...
Note: I am using {1..100} so that folder order starts from 1, 2, 3, 4...100 and not as 1, 10, 11,...100, 2, 20, 21, 22...
Is there any way I could find and copy images without changing the file extension?
Thank you in advance!

Assuming you want to use the subfolder name (number) as a suffix to the new
file name, would you please try:
#!/bin/bash
folder="folder" # directory name
base="1" # basename of the jpg file
ext="jpg" # extention of the jpg file
new="new" # directory name for the "new" files
mkdir -p "$folder/$new" # create "new" directory if nonexistent
while IFS= read -r -d "" f; do
n=${f#*$folder/}; n=${n%/$base.$ext}
echo cp -i -- "$f" "$folder/$new/$base($n).$ext"
done < <(find "$folder" -type f -regex "$folder/[0-9]+/$base\.$ext" -print0)
find "$folder" -type f -regex "$folder/[0-9]+/$base\.$ext" finds
all numbered folders which contains "1.jpg". You do not have to specify
the range between 1 and 100.
-print0 option uses a null character to delimit filenames. It is useful
to protect filenames which may contain special characters such as blank
characters.
The output of find command, matched filenames delimited by null characters,
is redirected to the read command within the while loop.
The -d "" option to the read command splits the input on null characters
corresponding to -print0.
n=${f#*$folder/}; n=${n%/$base.$ext} assigns n to the subfolder name
which contains the jpg file.
"$folder/$new/$base($n).$ext" constructs the new filename rearranging
the substrings.
If the output of the echo command looks okay, drop echo.

Do you accept a solution in two times?
Run your find command
Run this command:
for f in folder/new/*.~*~; do
idx="${f##*.}";
new=${f%.${idx}};
idx="${idx//\~/}";
ext="${new##*.}";
new="${new%.${ext}}";
new="${new}(${idx}).${ext}";
echo mv "$f" "$new";
done
Only based on bash remove %, #, %% and ## matching patterns.
get index pattern from original filename (f)
new filename (new) is original filename without index extension (~*~)
remove ~ characters in index token (idx)
get original filename extension (ext)
remove original filename extension in new filename
create new filename (with format as you want)
rename original filename to new filename
Notes:
Remove new line characters if you want (just here for presentation)
After test, remove echo before mv command.

Related

Renaming multiple files - first by finding the files in a set of subdirectories and then matching file names to a list containing the new file names

I have a set of files that are organised across a large number of sub-directories, for example:
/Image/Square001/Data
/Image/Square002/Data
/Image/Square003/Data
and so on.
All the files within these directories have a similar type of name but have different numbers within the name.
Triangle_284852_Data_222844_222846_20220302_034926.tiff
Triangle_284908_Data_222841_222843_20220302_035350.tiff
Triangle_284908_Data_222845_222843_20220302_035350.tiff
I had like to rename all of these files to include another number. The correct number needed has already been appended to a given filename within a text file (temp_names.txt)
Triangle_284852_Data_222844_222846_20220302_034926_1.tiff
Triangle_284908_Data_222841_222843_20220302_035350_63.tiff
Triangle_284908_Data_222845_222843_20220302_035350_12.tiff
I am trying to write a bash script to rename the files in the directory to match those in temp_names.txt
I think I need to use the find command, set up a while loop for each file to be matched based on their similarity and be renamed, but I am having trouble getting this right.
In the past, I have used something like:
for file in ./Image/Square*/Data/*.tiff
do
read line
mv "${file}" "${line}"
done < temp_names.txt
When the files are all in the same sub-directory and are read in the same order as they appear in my text file. However, this is not the case for these files and I need a new approach. I have been trying some variants of starting like this:
find /Images/Square*/Data/ -type f -name .tiff |
while IFS= read -d '' file_name; do
But I am unsure whether I should be using rename or mv in the next line, how to match the common parts between the filename in the directory and in the text file, and then how to add a changeable string (the number at the end) to the file name.
This is a script to do your requirement:
#!/bin/bash
find . -type f -name "Triangle_*.tiff" -print0 | while IFS= read -r -d '' file
do
# extract the filename, without extension
filename=$(basename "${file%.*}")
# Find that in the temp_names.txt file
newfilename=$(grep "$filename" temp_names.txt)
# extract the path of the file
filepath=$(dirname "$file")
# rename the file
mv -v "$file" "$filepath/$newfilename"
done
The find is recursive, so it will handle all files.
From each file, remove its extension, then grep for the name in the list of new filenames.
Since the file could be in any sub-directory, the path is extracted from the file found by find.
The mv changes the name of the file to the new name, under the same directory as the original file.
I put option -v to the mv to get some log of what is done. But you can remove it if not required.

Use bash to replace substring in filename with a new substring based on pairwise (old,new) values in a .csv file

I have a directory containing hundreds of image files, each named differently, such as:
abdogf.png
abcatf.png
abhorsef.png
I have created a changenames.csv file containing two columns with oldstring in the first column and newstring in the second such as:
"dog","woof"
"cat","miaow"
"horse","neigh"
These strings are currently in quotation marks, as shown.
I would like to invoke a bash command or .sh script from the command line to replace each oldstring substring with each newstring substring in the directory's filenames (not within file contents), such that the directory newly contains files called:
abwooff.png
abmiaowf.png
abneighf.png
instead of the original files.
I have tried various solutions such as https://superuser.com/questions/508731/find-and-replace-string-in-filenames/508758#508758 and How to find and replace part of filenames from list without success.
For example, I have tried invoking the following within the directory with the files:
#!/bin/bash
inputfile=${1}
while read line
do
IFS=',' read -a names <<< "${line}"
for file in `find . -maxdepth 1 -type f -name "*${names[0]}*"`; do
rename "s/${names[0]}/${names[1]}/" *
done
done < ${inputfile}
using the command line command test.sh changenames.csv.
This produces no error but makes no changes to the filenames.
I have also tried this solution https://stackoverflow.com/a/55866613/10456769 which generated an error in which #echo was not a recognised command.
Thank you in advance for any help.
You need to strip the double quotes off at first. The code tries to find
files such as *"cat"* which do not exit.
Moreover you do not need to execute the find command. You are not
using the variable file at all.
Would you please try the following:
while IFS=',' read -r old new; do
old=${old//\"/} # remove leading and trailing double-quotes
new=${new//\"/} # same as above
rename "s/$old/$new/" *
done < "$1"
The IFS=',' read -a names <<< "${line}" does not remove " from the input. Your filenames do not have " in them, so you have to remove them too.
Backticks ` are discouraged. Don't use them. Use $(....) instead.
"for file in `" is as bad as for file in $(cat) - it's a common bash antipattern. Don't use it - you will have problems with elements with spaces or tabs. Use while IFS= read -r line to read something like by line.
There is a problem with rename, there are two common versions of rename - GNU rename and perl rename. Your script seems to aim the perl version - make sure it is the one installed.
Let rename do the rename - there is no need for for file in find here.
If you do while read line and then IFS=, read <<<"$line" is duplicating the work, just do while IFS=, read -a names; do from the beginning.
So you could do:
# split the input on ','
while IFS=',' read -r pre post; do
# remove quotes
pre=${pre//\"/}
post=${post//\"/}
# do the rename
rename "s/${pre}/${post}/" *
done < ${inputfile}
I think I would do the following script that uses sed:
# find all files in a directory
find . -maxdepth 1 -type f |
# convert filenames into pairs (filename,new_filename) separated by newline
sed "
# hold the line in hold space
h
# replace the characters as in the other file
$(
# generate from "abc","def" -> s"abc"def"g
sed 's#"\([^"]*\)","\([^"]*\)"#s"\1"\2"g#' changenames.csv
)
# switch pattern and hold space
x
# append the line
G
# remove the line if substitute is the same
/^\(.*\)\n\1$/d
" |
# outputs two lines per each filename:
# one line with old filename and one line with new filename
# so just pass that to mv
xargs -l2 echo mv -v
and a one liner:
find . -maxdepth 1 -type f | sed "h;$(sed 's#"\([^"]*\)","\([^"]*\)"#s"\1"\2"g#' changenames.csv);x;G; /^\(.*\)\n\1$/d" | xargs -l2 echo mv -v
With the following recreation of files structure:
touch abdogf.png abcatf.png abhorsef.png
cat <<EOF >changenames.csv
"dog","woof"
"cat","miaow"
"horse","neigh"
EOF
The script outputs on repl:
mv -v ./abdogf.png ./abwooff.png
mv -v ./abcatf.png ./abmiaowf.png
mv -v ./abhorsef.png ./abneighf.png

save filename and information from the file into a two column txt doc. ubuntu terminal

I have a question regarding the manipulation and creation of text files in the ubuntu terminal. I have a directory that contains several 1000 subdirectories. In each directory, there is a file with the extension stats.txt. I want to write a piece of code that will run from the parent directory, and create a file with the name of all the stats.txt files in the first column, and then returns to me all the information from the 5th line of the same stats.txt file in the next column. The 5th line of the stats.txt file is a sentence of six words, not a single value.
For reference, I have successfully used the sed command in combination with find and cat to make a file containing the 5th line from each stats.txt file. I then used the ls command to save a list of all my subdirectories. I assumed both files would be in alphabetical order of the subdirectories, and thus easy to merge, but I was wrong. The find and cat functions, or at least my implementation of them, resulted in a file that appeared to be random in order (see below). No need to try to remedy this code, I'm open to all solutions.
# loop through subdirectories and save the 5th line of stats.txt as a different file.
for f in ~/*; do [ -d $f ] && cd "$f" && sed -n 5p *stats.txt > final.stats.txt done;
# find the final.stats.txt files and save them as a single file
find ./ -name 'final.stats.txt' -exec cat {} \; > compiled.stats.txt
Maybe something like this can help you get on track:
find . -name "*stats.txt" -exec awk 'FNR==5{print FILENAME, $0}' '{}' + > compiled.stats

Script for renaming files - removing dash chars from Android resource files

The following scripts finds all files in the current directory (recursively) and replaces dash - characters to underscore chars in file names
find . -type f -name '*.png' | while read FILE ; do
newfile="$(echo ${FILE} |sed -e 's/-/_/g')";
mv "${FILE}" "${newfile}" ;
done
given a file in the path that contains dashes, for example, drawable-hdpi/file-name.png, the script will try to rename it to drawable_hdpi/file_name.png - replacing the dash in the directory name as well as in the file name.
I would like to avoid modifying the directory path, and only rewrite the file name.
Any suggestions on how to modify the sed usage to skip the directory path?
The shell has some nifty string operators to chop parts of variables. Your shell manual page has all the details. Here's how I would use them:
find . -type f -name '*.png' |
while read FILE; do
dir=${FILE%/*}
newfile=$(echo "${FILE##*/}" |sed -e 's/-/_/g')
mv "${FILE}" "${dir}/${newfile}"
done
Explanation:
dir=${FILE%/*} chops the shortest part from the right that matches the /* glob, removing the slash and file name, giving the directory.
${FILE##*/} removes the longest part from the left matching */, i.e. leaves just the file name.

Recursively concatenating (joining) and renaming text files in a directory tree

I am using a Mac OS X Lion.
I have a folder: LITERATURE with the following structure:
LITERATURE > Y > YATES, DORNFORD > THE BROTHER OF DAPHNE:
Chapters 01-05.txt
Chapters 06-10.txt
Chapters 11-end.txt
I want to recursively concatenate the chapters that are split into multiple files (not all are). Then, I want to write the concatenated file to its parent's parent directory. The name of the concatenated file should be the same as the name of its parent directory.
For example, after running the script (in the folder structure shown above) I should get the following.
LITERATURE > Y > YATES, DORNFORD:
THE BROTHER OF DAPHNE.txt
THE BROTHER OF DAPHNE:
Chapters 01-05.txt
Chapters 06-10.txt
Chapters 11-end.txt
In this example, the parent directory is THE BROTHER OF DAPHNE and the parent's parent directory is YATES, DORNFORD.
[Updated March 6th—Rephrased the question/answer so that the question/answer is easy to find and understand.]
It's not clear what you mean by "recursively" but this should be enough to get you started.
#!/bin/bash
titlecase () { # adapted from http://stackoverflow.com/a/6969886/874188
local arr
arr=("${#,,}")
echo "${arr[#]^}"
}
for book in LITERATURE/?/*/*; do
title=$(titlecase ${book##*/})
for file in "$book"/*; do
cat "$file"
echo
done >"$book/$title"
echo '# not doing this:' rm "$book"/*.txt
done
This loops over LITERATURE/initial/author/BOOK TITLE and creates a file Book Title (where should a space be added?) from the catenated files in each book directory. (I would generate it in the parent directory and then remove the book directory completely, assuming it contains nothing of value any longer.) There is no recursion, just a loop over this directory structure.
Removing the chapter files is a bit risky so I'm not doing it here. You could remove the echo prefix from the line after the first done to enable it.
If you have book names which contain an asterisk or some other shell metacharacter this will be rather more complex -- the title assignment assumes you can use the book title unquoted.
Only the parameter expansion with case conversion is beyond the very basics of Bash. The array operations could perhaps also be a bit scary if you are a complete beginner. Proper understanding of quoting is also often a challenge for newcomers.
cat Chapters*.txt > FinaleFile.txt.raw
Chapters="$( ls -1 Chapters*.txt | sed -n 'H;${x;s/\
//g;s/ *Chapters //g;s/\.txt/ /g;s/ *$//p;}' )"
mv FinaleFile.txt.raw "FinaleFile ${Chapters}.txt"
cat all txt at once (assuming name sorted list)
take chapter number/ref from the ls of the folder and with a sed to adapt the format
rename the concatenate file including chapters
Shell doesn't like white space in names. However, over the years, Unix has come up with some tricks that'll help:
$ find . -name "Chapters*.txt" -type f -print0 | xargs -0 cat >> final_file.txt
Might do what you want.
The find recursively finds all of the directory entries in a file tree that matches the query (In this case, the type must be a file, and the name matches the pattern Chapter*.txt).
Normally, find separates out the directory entry names with NL, but the -print0 says to separate out the entries names with the NUL character. The NL is a valid character in a file name, but NUL isn't.
The xargs command takes the output of the find and processes it. xargs gathers all the names and passes them in bulk to the command you give it -- in this case the cat command.
Normally, xargs separates out files by white space which means Chapters would be one file and 01-05.txt would be another. However, the -0 tells xargs, to use NUL as a file separator -- which is what -print0 does.
Thanks for all your input. They got me thinking, and I managed to concatenate the files using the following steps:
This script replaces spaces in filenames with underscores.
#!/bin/bash
# We are going to iterate through the directory tree, up to a maximum depth of 20.
for i in `seq 1 20`
do
# In UNIX based systems, files and directories are the same (Everything is a File!).
# The 'find' command lists all files which contain spaces in its name. The | (pipe) …
# … forwards the list to a 'while' loop that iterates through each file in the list.
find . -name '* *' -maxdepth $i | while read file
do
# Here, we use 'sed' to replace spaces in the filename with underscores.
# The 'echo' prints a message to the console before renaming the file using 'mv'.
item=`echo "$file" | sed 's/ /_/g'`
echo "Renaming '$file' to '$item'"
mv "$file" "$item"
done
done
This script concatenates text files that start with Part, Chapter, Section, or Book.
#!/bin/bash
# Here, we go through all the directories (up to a depth of 20).
for D in `find . -maxdepth 20 -type d`
do
# Check if the parent directory contains any files of interest.
if ls $D/Part*.txt &>/dev/null ||
ls $D/Chapter*.txt &>/dev/null ||
ls $D/Section*.txt &>/dev/null ||
ls $D/Book*.txt &>/dev/null
then
# If we get here, then there are split files in the directory; we will concatenate them.
# First, we trim the full directory path ($D) so that we are left with the path to the …
# … files' parent's parent directory—We will write the concatenated file here. (✝)
ppdir="$(dirname "$D")"
# Here, we concatenate the files using 'cat'. The 'awk' command extracts the name of …
# … the parent directory from the full directory path ($D) and gives us the filename.
# Finally, we write the concatenated file to its parent's parent directory. (✝)
cat $D/*.txt > $ppdir/`echo $D|awk -F'/' '$0=$(NF-0)'`.txt
fi
done
Now, we delete all the files that we concatenated so that its parent directory is left empty.
find . -name 'Part*' -delete
find . -name 'Chapter*' -delete
find . -name 'Section*' -delete
find . -name 'Book*' -delete
The following command will delete empty directories. (✝) We wrote the concatenated file to its parent's parent directory so that its parent directory is left empty after deleting all the split files.
find . -type d -empty -delete
[Updated March 6th—Rephrased the question/answer so that the question/answer is easy to find and understand.]

Resources