Convert all file extensions to lower-case

前端 未结 11 1929
生来不讨喜
生来不讨喜 2020-12-07 14:06

I\'m trying to lower-case all my extensions regardless of what it is. So far, from what I\'ve seen, you have to specify what file extensions you want to convert to lower-cas

相关标签:
11条回答
  • 2020-12-07 14:48

    recursively for all one fine solution:

    find -name '*.JPG' | sed 's/\(.*\)\.JPG/mv "\1.JPG" "\1.jpg"/' |sh
    

    The above recursively renames files with the extension "JPG" to files with the extension "jpg"

    0 讨论(0)
  • 2020-12-07 14:49

    Solution

    You can solve the task in one line:

    find . -name '*.*' -exec sh -c '
      a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/");
      [ "$a" != "$0" ] && mv "$0" "$a" ' {} \;
    

    Note: this will break for filenames that contain newlines. But bear with me for now.

    Example of usage

    $ mkdir C; touch 1.TXT a.TXT B.TXT C/D.TXT
    $ find .
    .
    ./C
    ./C/D.TXT
    ./1.TXT
    ./a.TXT
    ./B.TXT
    
    $ find . -name '*.*' -exec sh -c 'a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/"); [ "$a" != "$0" ] && mv "$0" "$a" ' {} \;
    
    $ find .
    .
    ./C
    ./C/D.txt
    ./a.txt
    ./B.txt
    ./1.txt
    

    Explanation

    You find all files in current directory (.) that have period . in its name (-name '*.*') and run the command for each file:

    a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/");
    [ "$a" != "$0" ] && mv "{}" "$a"
    

    That command means: try to convert file extension to lowercase (that makes sed):

    $ echo 1.txt | sed -r "s/([^.]*)\$/\L\1/"
    1.txt
    $ echo 2.TXT | sed -r "s/([^.]*)\$/\L\1/"
    2.txt
    

    and save the result to the a variable.

    If something was changed [ "$a" != "$0" ], rename the file mv "$0" "$a".

    The name of the file being processed ({}) passed to sh -c as its additional argument and it is seen inside the command line as $0. It makes the script safe, because in this case the shell take {} as a data, not as a code-part, as when it is specified directly in the command line. (I thank @gniourf_gniourf for pointing me at this really important issue).

    As you can see, if you use {} directly in the script, it's possible to have some shell-injections in the filenames, something like:

    ; rm -rf * ;
    

    In this case the injection will be considered by the shell as a part of the code and they will be executed.

    While-version

    Clearer, but a little bit longer, version of the script:

    find . -name '*.*' | while IFS= read -r f
    do
      a=$(echo "$f" | sed -r "s/([^.]*)\$/\L\1/");
      [ "$a" != "$f" ] && mv "$f" "$a"
    done
    

    This still breaks for filenames containing newlines. To fix this issue, you need to have a find that supports -print0 (like GNU find) and Bash (so that read supports the -d delimiter switch):

    find . -name '*.*' -print0 | while IFS= read -r -d '' f
    do
      a=$(echo "$f" | sed -r "s/([^.]*)\$/\L\1/");
      [ "$a" != "$f" ] && mv "$f" "$a"
    done
    

    This still breaks for files that contain trailing newlines (as they will be absorbed by the a=$(...) subshell. If you really want a foolproof method (and you should!), with a recent version of Bash (Bash≥4.0) that supports the ,, parameter expansion here's the ultimate solution:

    find . -name '*.*' -print0 | while IFS= read -r -d '' f
    do
      base=${f%.*}
      ext=${f##*.}
      a=$base.${ext,,}
      [ "$a" != "$f" ] && mv -- "$f" "$a"
    done
    

    Back to the original solution

    Or in one find go (back to the original solution with some fixes that makes it really foolproof):

    find . -name '*.*' -type f -exec bash -c 'base=${0%.*} ext=${0##*.} a=$base.${ext,,}; [ "$a" != "$0" ] && mv -- "$0" "$a"' {} \;
    

    I added -type f so that only regular files are renamed. Without this, you could still have problems if directory names are renamed before file names. If you also want to rename directories (and links, pipes, etc.) you should use -depth:

    find . -depth -name '*.*' -type f -exec bash -c 'base=${0%.*} ext=${0##*.} a=$base.${ext,,}; [ "$a" != "$0" ] && mv -- "$0" "$a"' {} \;
    

    so that find performs a depth-first search.

    You may argue that it's not efficient to spawn a bash process for each file found. That's correct, and the previous loop version would then be better.

    0 讨论(0)
  • 2020-12-07 14:50

    I got success with this command.

    rename JPG jpg *.JPG
    

    Where rename is a command that tells the shell to rename every occurrence of JPG to jpg in the current folder with all filenames having extension JPG.

    If you see Bareword "JPG" not allowed while "strict subs" in use at (eval 1) line 1 with this approach try:

    rename 's/\.JPG$/.jpg/' *.JPG
    
    0 讨论(0)
  • 2020-12-07 14:51

    If your only interested in certain file extensions like converting all higher case "JPG" extensions to lower case "jpg" You could use the command line utility rename like so. CD into directory you want to change. Then

    rename -n 's/\.JPG$/\.jpg/' *
    

    Use -n option to test what will be changed, then when you happy with results use without like so

    rename  's/\.JPG$/\.jpg/' *
    
    0 讨论(0)
  • 2020-12-07 14:52

    If you are using ZSH:

    zmv '(*).(*)' '$1.$2:l'
    

    If you get zsh: command not found: zmv then simply run:

    autoload -U zmv
    

    And then try again.

    Thanks to this original article for the tip about zmv and the ZSH documentation for the lowercase/uppercase substitution syntax.

    0 讨论(0)
提交回复
热议问题