0

Is there a way to get the rename command to print JUST the output file name (not the XXX renamed as prefix or the brackets)? I'd rather not use regex unless that's the only choice.

Why I'm asking: I want to do several renames in succession on a single file (well, a list of files actually) so that the file names aren't ugly. It's difficult because if the first rename change it, there's no easy way to get the new name of the file that can become input to the second rename command.

Example

This should rename files I've downloaded from the web that have %20 in them to . In a subsequent command in the same pipeline I might want to rename %28 as (. And I don't want to use a single rename command because of the regex complexity (if that's even possible).

find -iname "*%20*" | xargs -n 1 rename --some-option 's/%20/ /g' | xargs -n 1 rename --some-option 's/%28/(/g' 
2
  • As far as I know, the Perl rename command doesn't read from standard input, so you can't put it in a pipeline. If you could describe and show what it is you'd want to do, I'm sure someone would be able to give you some idea of what could be done.
    – Kusalananda
    CommentedDec 19, 2019 at 21:33
  • Sorry, I don't mean Perl's rename. I just mean this man7.org/linux/man-pages/man1/rename.1.html . But yes I will add an example.CommentedDec 19, 2019 at 21:34

3 Answers 3

1

Consider using a sequence of sed commands to transform the starting filename into the ultimate filename, then doing onemv to actually rename the file:

for i in *%* do printf "mv -v '%s' '%s'\n" "$i" \ "$( sed -e 's/%20/ /g' \ -e 's/%28/(/g' \ <<< "$i" )" done 

That command list actually doesn't change your files, which is a good thing. It just outputs the mv commands that you can use to actually do what you're trying to do.

Let's create some sample files containing %20 and %28 occurrences:

$ for i in foo bar farkle > do > touch $i"%20".txt; touch $i"%28".txt; touch $i"%20-%28".txt > done $ ls -1 bar%20-%28.txt bar%20.txt bar%28.txt farkle%20-%28.txt farkle%20.txt farkle%28.txt foo%20-%28.txt foo%20.txt foo%28.txt 

Now let's run the command list given at the outset:

$ for i in *%* > do > printf "mv -v '%s' '%s'\n" "$i" \ > "$(sed -e 's/%20/ /g' -e 's/%28/(/g' <<< "$i")" > done mv -v 'bar%20-%28.txt' 'bar -(.txt' mv -v 'bar%20.txt' 'bar .txt' mv -v 'bar%28.txt' 'bar(.txt' mv -v 'farkle%20-%28.txt' 'farkle -(.txt' mv -v 'farkle%20.txt' 'farkle .txt' mv -v 'farkle%28.txt' 'farkle(.txt' mv -v 'foo%20-%28.txt' 'foo -(.txt' mv -v 'foo%20.txt' 'foo .txt' mv -v 'foo%28.txt' 'foo(.txt' 

If that sequence of mv commands looks correct to you, then up-arrow to run the command again, but this time add | sh at the end, to actually execute those mv commands instead of displaying them.

3
  • Hmmmm, while that's not as elegant as in a perfect world, my scripts have become so ugly that this might be as good a solution as any. Thanks.CommentedDec 19, 2019 at 23:28
  • 1
    @SridharSarnobat If you wanted to, you could write the sed step as sed ... | sed ... | sed ... but chaining multiple -e 's/this/that/g' replacements does the same thing. I'll see if I can edit it to look a little nicer.
    – Jim L.
    CommentedDec 20, 2019 at 0:10
  • The more I think about this the more I like the pipeline of sed operations.CommentedDec 30, 2019 at 3:16
0

This seems to be, at it's core, an exercise in URL decoding, which is something of a solved problem.

Save this as, say, rename_script.sh:

DECODED="$(awk -niord '{printf RT?$0chr("0x"substr(RT,2)):$0}' RS=%.. <<< "${1}")" [ ! -f "${1}" ] && echo "Source not found" && exit 1 [ -f "${DECODED}" ] && echo "Target already exists" && exit 1 [ "${1}" != "${DECODED}" ] && mv -i "${1}" "${DECODED}" || echo "Nothing to do" 

And invoke thus:

find -name "*%*" -print0 | xargs -n1 -0 ./rename_script.sh 

Heavy credit to this answer: https://stackoverflow.com/a/14000368/2858703

2
  • 1
    Can you explain how the awk command works? It looks as if it requires GNU awk, right?
    – Kusalananda
    CommentedDec 20, 2019 at 19:18
  • This is useful for related cleansing, but I'd rather not make that assumption. There is other junk in my filenames. I'm actually using youtube-dl and my video filenames contain things like spaces before the dot extension, contain double extensions like .mp4.mkv. Some even contain emoji (which I'm trying to decide if I want to remove or not).CommentedDec 20, 2019 at 19:20
0
find . -type f -name '*%*' -execdir bash -c ' name=$1 name=${name//%20/ } # replace each %20 by a space name=${name//%28/(} # replace each %28 by a ( mv "$1" "$name"' bash {} \; 

This uses find to locate any regular file in or beneath the current directory that has a % character in their name. For each such file, a short bash script is executed which does the two substitutions that you mention on the given filename, and then renames the file.

It would be easy to add other parameter substitutions to this to delete or change other strings in the filenames.

A possibly quicker way would be to write a simple loop to do the same thing (assuming the bash shell for this particular variation):

shopt -s nullglob dotglob globstar for pathname in ./**/*%*; do # skip pathnames to non-regular (or links to non-regular) files [[ ! -f "$pathname" ]] && continue name=${pathname##*/} # strip off directory components name=${name//%20/ } # ... so that we don't accidentally name=${name//%28/(} # ... modify the directory path # the destination is made up by the directory path # (original pathname with filename stripped off) # followed by the new name mv "$pathname" "${pathname%/*}/$name" done 

The nullglob shell option makes the pattern ./**/*%* expand to nothing if it doesn't match anything (it would ordinarily be left unexpanded), dotglob allows globbing patterns to mach hidden names, and globstar enables the use of ** to match down into subdirectories "recursively".

Adapting that second loop into something that uses find for generating the pathnames rather than a globbing pattern (e.g. for running under bash releases earlier than release 4, which didn't have **):

find . -type f -name '*%*' -exec bash -c ' for pathname do name=${pathname##*/} # strip off directory components name=${name//%20/ } # ... so that we don't accidentally name=${name//%28/(} # ... modify the directory path # the destination is made up by the directory path # (original pathname with filename stripped off) # followed by the new name mv "$pathname" "${pathname%/*}/$name" done' bash {} + 
2
  • Ah another interesting solution, use regex replace in a shell variable reference (which I only found out about a few weeks ago but didn't think I'd ever use).CommentedDec 19, 2019 at 23:36
  • 2
    @SridharSarnobat This uses no regular expression. The only patters used are filename globbing patterns.
    – Kusalananda
    CommentedDec 19, 2019 at 23:37

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.