Bash script to rename file extension - bash

#!/bin/bash
if [[ "$#" -lt 3 ]]; then
echo "USAGE: enter the following arguments:
1st argument = starting directory path (e.g. . for working directory)
2nd argument = old file extension (e.g. txt)
3rd argument = new file extension (e.g. html
Note: Do not enter any . or \. for the 2nd and 3rd arguments
"
else
find "$1" -name *."$2"* -exec rename 's/\."${2}"$/."${3}"/' '{}' \;
fi
Example input:
bash rename_file_extensions.sh . txt html
Example output:
Use of uninitialized value $2 in regexp compilation at (eval 6) line 1.
line 1 contains the if statement above. Argument $2 is txt. So, I am confused as to what it is referring to.
I can run the following line and it works perfectly, but I want to have it accept bash arguments:
find . -name "*.txt.html*" -exec rename 's/\.txt.html.html.html$/.html/' '{}' \;
In this case, as you can see, there were a lot of incorrect file extensions. This line corrected all of them.
I tried editing the find line to:
find "${1}" -name '*."$2"*' -exec rename 's/\."${2}"$/."${3}"/' '{}' \;
This allowed me to move forward without an error; however, there was no output, and it did not change any txt extensions to html when I ran the following command, bash rename_file_extensions.sh . txt html.
Please help!
Thank you!

to get arguments interpreted by the shell, drop the single quotes:
try
find "$1" -name "*.$2*" -exec rename "s/\.${2}\$/.${3}/" '{}' \;
or (if you want to keep quotes)
find "$1" -name "*.$2*" -exec rename 's/\.'"${2}"'$/.'"${3}"'/' '{}' \;
(and I would enclose the full argument of -name in double quotes or bash could expand the argument directly if some files match in the current directory)

Related

Append a file's last modification date to its name

I'm struggling to find a way to get the last modification date of every file in a folder and subfolders and append them to their names accordingly. So far I can only append a custom text per file, in this case it's _Suffix from this command: find * -exec mv {} {}_Suffix \;
Maybe this isn't the best way since my text is inserted at the very end of the file so it changes the file's extension but at least it works :)
But I want to know how to insert the last modification date instead of _Suffix and do that for every file recursively.
find . -type f -exec bash -c 'for arg; do arg=${arg#./} mod=$(stat -c %x "$arg") base=${arg%.*} ext=${arg#$base}; echo mv -i "$arg" "${base}_${mod%% *}$ext"; done' _ {} +
(multiline version for readability):
find . -type f -exec bash -c 'for arg; do
arg=${arg#./} mod=$(stat -c %x "$arg") base=${arg%.*} ext=${arg#$base}
echo mv -i "$arg" "${base}_${mod%% *}$ext"
done' _ {} +
I left the echo there for you to see what it's going to run before actually running it. Remove it once you're sure you want to move the files.
It adds what you want before the extension but it will completely FAIL if:
A filename doesn't contain a dot
That file is in a folder path which does contain a dot
It will also not work properly on files with double extensions, e.g. .tar.gz
Explanation:
I'm passing all files to a bash script with find . -type f -exec bash -c '...' _ {} +
The bash script does the same action for all files: get the modification date, discover the basename and the .extension, then rename the file to basename_date.extension

Shell script does not find file with space in name

I created two scripts to make a checksum of a lot of files, in one of them I use the 'find' command to search the files in a specific directory, but when the file has a name with space character, file one.txt for example, the script takes this as two files: file and one. I know that error is in the 'find' command line, but I don't know what I do wrong.
My script:
#!/bin/bash
find $1 -type f -exec bash ./task2.sh "{}" \;
In order to search for a file with spaces in the name you either have to enclose the argument in quotes or escape the spaces. Like:
find "test file.txt" or alternatively find test\ file.txt.
The easiest fix in your particular use case is to enclose $1 in quotes:
find "$1" -type f -exec bash ./task2.sh "{}" \;

Bash script as .command not executing properly

I have a simple script/command:
When I run it from shell:
txt=testfile
find ~/Desktop/Rory/Test -type f -exec grep $txt {} \; -exec mkdir $txt \;
The result is:
Binary file ./TEST.zip matches
and it makes a new directory named testfile
When I save it as a .command:
echo "cd: \c"
read dir
echo "txt: \c"
read str
find $dir -type f -exec grep $str {} \; -exec mkdir $str \;
chmod 755 and double click on it I get:
Last login: Wed Apr 2 20:44:14 on ttys004
zipher:~ Rory$ /Users/Rory/Desktop/CD.command ; exit;
cd: \c
/Users/Rory/Desktop/Test
txt: \c
txt
Then it proceeds to go to hell in a handbasket, recursively going into places the other command never ventures — I have to ^C it because it's gone bonkers. It doesn't create the new directory either.. where did I screw the pooch?
I have no specific explanation, but a few pointers:
Does your script have a bash shebang line (is the first line of the file #!/usr/bin/env bash)? I've seen OSX act strangely without it.
I assume \c in the prompt strings is meant to suppress the trailing newline - this won't work by default in bash - you either need to invoke echo with -e, or - preferably - just use printf - e.g., printf "cd: ".
I suggest you double-quote the variable references in your find command so that the command won't break if a path or string entered contains embedded spaces or another char. with special meaning to the shell.
So far we get:
printf "cd: "
read dir
printf "txt: "
read str
find "$dir" -type f -exec grep -l "$str" {} \; -exec mkdir "$str" \;
There's more:
Note that find process the entire subtree of the specified directory; if you only want to match files in the current directory, add -maxdepth 1.
Be aware that -exec executes commands in the current directory, regardless of where the matching file was found - if you want all directories to be created in the input directory, use -exec mkdir "$dir/$str" \; by contrast, if you want to create the directory created in whatever subdirectory each matching file was found, use -execdir mkdir "$str" \;

Bash script copying certain type of file to another location

I was thinking if using a BASH script is possible without manually copying each file that is in this parent directory
"/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk
/System/Library/PrivateFrameworks"
So in this folder PrivateFrameworks, there are many subfolders and in each subfolder it consists of the file that I would like to copy it out to another location. So the structure of the path looks like this:
-PrivateFrameworks
-AccessibilityUI.framework
-AccessibilityUI <- copy this
-AccountSettings.framework
-AccountSettings <- copy this
I do not want the option of copying the entire content in the folder as there might be cases where the folders contain files which I do not want to copy. So the only way I thought of is to copy by the file extension. However as you can see, the files which I specified for copying does not have an extension(I think?). I am new to bash scripting so I am not familiar if this can be done with it.
To copy all files in or below the current directory that do not have extensions, use:
find . ! -name '*.*' -exec cp -t /your/destination/dir/ {} +
The find . command looks for all files in or below the current directory. The argument -name '*.*' would restrict that search to files that have extensions. By preceding it with a not (!), however, we get all files that do not have an extension. Then, -exec cp -t /your/destination/dir/ {} + tells find to copy those files to the destination.
To do the above starting in your directory with the long name, use:
find "/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk/System/Library/PrivateFrameworks" ! -name '*.*' -exec cp -t /your/destination/dir/ {} +
UPDATE: The unix tag on this question has been removed and replaced with a OSX tag. That means we can't use the -t option on cp. The workaround is:
find . ! -name '*.*' -exec cp {} /your/destination/dir/ \;
This is less efficient because a new cp process is created for every file moved instead of once for all the files that fit on a command line. But, it will accomplish the same thing.
MORE: There are two variations of the -exec clause of a find command. In the first use above, the clause ended with {} + which tells find to fill up the end of command line with as many file names as will fit on the line.
Since OSX lacks cp -t, however, we have to put the file name in the middle of the command. So, we put {} where we want the file name and then, to signal to find where the end of the exec command is, we add a semicolon. There is a trick, though. Because bash would normally consume the semicolon itself rather than pass it on to find, we have to escape the semicolon with a backslash. That way bash gives it to the find command.
sh SCRIPT.sh copy-from-directory .extension copy-to-directory
FROM_DIR=$1
EXTENSION=$2
TO_DIR=$3
USAGE="""Usage: sh SCRIPT.sh copy-from-directory .extension copy-to-directory
- EXAMPLE: sh SCRIPT.sh PrivateFrameworks .framework .
- NOTE: 'copy-to-directory' argument is optional
"""
## print usage if less than 2 args
if [[ $# < 2 ]]; then echo "${USAGE}" && exit 1 ; fi
## set copy-to-dir default args
if [[ -z "$TO_DIR" ]] ; then TO_DIR=$PWD ; fi
## DO SOMETHING...
## find directories; find target file;
## copy target file to copy-to-dir if file exist
find $FROM_DIR -type d | while read DIR ; do
FILE_TO_COPY=$(echo $DIR | xargs basename | sed "s/$EXTENSION//")
if [[ -f $DIR/$FILE_TO_COPY ]] ; then
cp $DIR/$FILE_TO_COPY $TO_DIR
fi
done

Rename files and directories using substitution and variables

I have found several similar questions that have solutions, except they don't involve variables.
I have a particular pattern in a tree of files and directories - the pattern is the word TEMPLATE. I want a script file to rename all of the files and directories by replacing the word TEMPLATE with some other name that is contained in the variable ${newName}
If I knew that the value of ${newName} was say "Fred lives here", then the command
find . -name '*TEMPLATE*' -exec bash -c 'mv "$0" "${0/TEMPLATE/Fred lives here}"' {} \;
will do the job
However, if my script is:
newName="Fred lives here"
find . -name '*TEMPLATE*' -exec bash -c 'mv "$0" "${0/TEMPLATE/${newName}}"' {} \;
then the word TEMPLATE is replaced by null rather than "Fred lives here"
I need the "" around $0 because there are spaces in the path name, so I can't do something like:
find . -name '*TEMPLATE*' -exec bash -c 'mv "$0" "${0/TEMPLATE/"${newName}"}"' {} \;
Can anyone help me get this script to work so that all files and directories that contain the word TEMPLATE have TEMPLATE replaced by whatever the value of ${newName} is
eg, if newName="A different name" and a I had directory of
/foo/bar/some TEMPLATE directory/with files then the directory would be renamed to
/foo/bar/some A different name directory/with files
and a file called some TEMPLATE file would be renamed to
some A different name file
You have two options.
1) The easiest solution is export newName. If you don't export the variable, then it's not available in subshells, and bash -c is a subshell. That's why you're getting TEMPLATE replaced by nothing.
2) Alternatively, you can try to construct a correctly quoted command line containing the replacement of $newName. If you knew that $newName were reasonably well-behaved (no double quotes or dollar signs, for example), then it's easy:
find . -name '*TEMPLATE*' \
-exec bash -c 'mv "$0" "${0/TEMPLATE/'"${newName}"'}"' {} \;
(Note: bash quoting is full of subtleties. The following has been edited several times, but I think it is now correct.)
But since you can't count on that, probably, you need to construct the command line by substituting both the filename and the substitution as command line parameters. But before we do that, let's fix the $0. You shouldn't be using $0 as a parameter. The correct syntax is:
bash -c '...$1...$1...' bash "argument"
Note the extra bash (many people prefer to use _); it's there to provide a sensible name for the subprocess.
So with that in mind:
find . -name '*TEMPLATE*' \
-exec bash -c 'mv "$1" "${1/TEMPLATE/$2}"' bash {} "$newName" \;
You an get around having to use quotes with IFS=$'\n' and since bash -c is a subshell an export of any variable is required. This works:
#!/bin/bash
IFS=$'\n'
export newName="Fred lives here"
find . -name '*TEMPLATE*' -exec bash -c 'mv "$0" "${0/TEMPLATE/${newName}}"' {} \;
If you do not mind two more lines and would like a script that is easier to read (no export required):
#!/bin/bash
IFS=$'\n'
newName="Fred lives here"
for file in $(find . -name '*TEMPLATE*'); do
mv ${file} ${file/TEMPLATE/${newName}}
done

Resources