1

The user inputs a file type they are looking for; it is stored in $arg1; the file type they would like to change them is stored as $arg2. I'm able to find what I'm looking for, but I'm unsure of how to keep the filename the same but just change the type... ie., file1.txt into file1.log.

find . -type f  -iname "*.$arg1" -exec mv {} \;
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Elroy Jetson
  • 938
  • 11
  • 27
  • @Kenavoz The alleged duplicate deals with files in one directory only. I'm pretty sure there *is* a duplicate somewhere, but I can't find it right now. – Benjamin W. Sep 10 '16 at 04:42

4 Answers4

3

To enable the full power of shell parameter expansions, you can call bash -c in your exec action:

find . -type f -iname "*.$arg1" \
    -exec bash -c 'echo mv "$1" "${1/%.*/$1}"' _ {} "$arg2" \;

We add {} and "$arg2" as a parameters to bash -c, so they become accessible within the command as $0 and $1. ${0%.*} removes the extension, to be replaced by whatever $arg2 expands to.

As it is, the command just prints the mv commands it would execute; to actually rename the files, the echo has to be removed.

The quoting is relevant: the argument to bash -c is in single quotes to prevent $0 and $1 from being expanded prematurely, and the two arguments to mv, and arg2 are also quoted to deal with file names with spaces in them.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
  • Could there be ref. to what the `_` is in this case? is it [this parameter from the shell expansions page](https://www.gnu.org/software/bash/manual/bash.html#index-_005f)? – Ari May 26 '22 at 08:30
2

I would do it like so:

find . -type f  -iname "*.$arg1" -print0 |\
    while IFS= read -r -d '' file; do
        mv -- "$file" "${file%$arg1}$arg2"
    done

I took your find command and fed its output to a while loop. Within that loop, I am doing the actual renaming. This way I have the name of the file as a variable that I can manipulate using bash's string manipulation operations.

redneb
  • 21,794
  • 6
  • 42
  • 54
  • You are much appreciated. Trying to learn this bash stuff and it's kicking my butt. Thanks – Elroy Jetson Sep 10 '16 at 02:46
  • 1
    FWIW, you don't need the backslash; if a line ends with a pipe, the pipeline automatically continues on the next line with no need to escape the newline. – Mark Reed Sep 10 '16 at 04:36
2

Combining the find -exec bash idea with the bash loop idea, you can use the + terminator on the -exec to tell find to pass multiple filenames to a single invocation of the bash command. Pass the new type as the first argument - which shows up in $0 and so is conveniently skipped by a for loop over the rest of the command-line arguments - and you have a pretty efficient solution:

find . -type f -iname "*.$arg1" -exec bash -c \
  'for arg; do mv "$arg" "${arg%.*}.$0"; done' "$arg2" {} +

Alternatively, if you have either version of the Linux rename command, you can use that. The Perl one (a.k.a. prename, installed by default on Ubuntu and other Debian-based distributions; also available for OS X from Homebrew via brew install rename) can be used like this:

find . -type f -iname "*.$arg1" -exec rename 's/\Q'"$arg1"'\E$/'"$arg2"'/' {} +

That looks a bit ugly, but it's really just the s/old/new/ substitution command familiar from many UNIX tools. The \Q and \E around $arg1 keep any weird characters inside the suffix from being interpreted as regular expression metacharacters that might match something unexpected; the $ after the \E makes sure the pattern only matches at the end of the filename.

The pattern-based version installed by default on Red Hat-based Linux distros (Fedora, CentOS, etc) is simpler:

find . -type f -iname "*.$arg1" -exec rename ".$arg1" ".$arg2" {} +

but it's also dumber: if you rename .com .exe stackoverflow.com_scanner.com, you'll get a file named stackoverflow.exe_scanner.exe.

Mark Reed
  • 91,912
  • 16
  • 138
  • 175
1

If you have perl based rename command

Sample directory:

$ find
.
./a"bn.txt
./t2.abc
./abc
./abc/t1.txt
./abc/list.txt
./a bc.txt

Sample args:

$ arg1='txt'
$ arg2='log'

Dry run:

$ find -type f -iname "*.$arg1" -exec rename -n "s/$arg1$/$arg2/" {} +
rename(./a"bn.txt, ./a"bn.log)
rename(./abc/t1.txt, ./abc/t1.log)
rename(./abc/list.txt, ./abc/list.log)
rename(./a bc.txt, ./a bc.log)

Remove -n option once it is okay:

$ find -type f -iname "*.$arg1" -exec rename "s/$arg1$/$arg2/" {} +
$ find
.
./a bc.log
./t2.abc
./abc
./abc/list.log
./abc/t1.log
./a"bn.log
Sundeep
  • 23,246
  • 2
  • 28
  • 103