5

I have a directory that has .zip files and other files (all files have an extension), and I want to add a suffix to all files without the .zip extension. I am able to pick off a suffix and place it into a variable $suffix, and so I tried the following code:

ls -I "*.zip"| xargs -I {} mv {} {}_"$suffix"

This lists all files without .zip and is close (but wrong). It incorrectly yields the following results on file.csv:

file.csv_suffix

I want file_suffix.csv -- how can I edit my code to retain the extension on the file?

  • note, ls will also print folder names – RomanPerekhrest Oct 5 '17 at 13:35
  • All these files I'm extracting from the .zip files will have an extension, primarily .csv. Not recursive, just in the particular directory. – kstats9pt3 Oct 5 '17 at 14:37
3

find + bash approaches:

export suffix="test"

a) with find -exec action:

find your_folder -type f ! -name "*.zip" -exec bash -c 'f=$1; if [[ "$f" =~ .*\.[^.]*$ ]]; then ext=".${f##*\.}"; else ext=""; fi; mv "$f" "${f%.*}_$suffix$ext"' x {} \;


b) Or with bash while loop:

find your_folder/ -type f ! -name "*.zip" -print0 | while read -d $'\0' f; do 
    if [[ "$f" =~ .*\.[^.]*$ ]]; then
        ext=".${f##*\.}"
    else 
        ext=""
    fi
    mv "$f" "${f%.*}_$suffix$ext"
done
  • You all know more than I do but this works fine for me. Has solid condition tests and is concise. Thanks. – kstats9pt3 Oct 5 '17 at 14:48
  • About embedding {} in shell code: unix.stackexchange.com/a/156010/116858 – Kusalananda Oct 5 '17 at 14:49
  • @RomanPerekhrest Because you're not passing any arguments to the bash -c shell. Try adding find-bash {} to the end before \;. – Kusalananda Oct 5 '17 at 15:06
4

Using ls is a bit hazardous. See Why *not* parse `ls`?

You will also have to pick apart the filename, otherwise you just append $suffix to the end of it, as you discovered.

Here follows a solution using find, and one without find.


find . -type f ! -name '*.zip' -exec sh -c 'suffix="$1"; shift; for n; do p=${n%.*}; s=${n##*.}; [ ! -e "${p}_$suffix.$s" ] && mv "$n" "${p}_$suffix.$s"; done' sh "$suffix" {} +

This will find all regular files in or somewhere under the current directory, whose names does not end with .zip.

Then the following shell script will be invoked with a list of those files:

suffix="$1"  # the suffix is passed as the first command line argument
shift        # shift it off $@
for n; do    # loop over the remaining names in $@
    p=${n%.*}    # get the prefix of the file path up to the last dot
    s=${n##*.}   # get the extension of the file after the last dot

    # rename the file if there's not already a file with that same name
    [ ! -e "${p}_$suffix.$s" ] && mv "$n" "${p}_$suffix.$s"
done

Testing:

$ touch file{1,2,3}.txt file{a,b,c}.zip
$ ls
file1.txt file2.txt file3.txt filea.zip fileb.zip filec.zip

$ suffix="notZip"
$ find . -type f ! -name '*.zip' -exec sh -c 'suffix="$1"; shift; for n; do p=${n%.*}; s=${n##*.}; [ ! -e "${p}_$suffix.$s" ] && mv "$n" "${p}_$suffix.$s"; done' sh "$suffix" {} +

$ ls
file1_notZip.txt    file3_notZip.txt    fileb.zip
file2_notZip.txt    filea.zip           filec.zip

The shell script above can be run independently of find if the number of files are not terribly large and if you don't need to recurse into subdirectories (only slightly modified to skip non-file names):

#!/bin/sh

suffix="$1"  # the suffix is passed as the first command line argument
shift        # shift it off $@
for n; do    # loop over the remaining names in $@

    [ ! -f "$n" ] && continue  # skip names of things that are not regular files

    p=${n%.*}    # get the prefix of the file path up to the last dot
    s=${n##*.}   # get the extension of the file after the last dot

    # rename the file if there's not already a file with that same name
    [ ! -e "${p}_$suffix.$s" ] && mv "$n" "${p}_$suffix.$s"
done

With bash, you would run this on the files in a directory like this:

$ shopt -s extglob
$ ./script.sh "notZip" !(*.zip)

With the extglob shell option set in bash, !(*.zip) would match all names in the current directory that does not end with .zip.

3

In bash:

shopt -s extglob
suffix=yoursuffix
for entry in !(*.zip)
do
  [[ -f "$entry" ]] || continue
  base=${entry%.*}
  if [[ "$entry" =~ \. ]]
  then
    ext=${entry##*.}
    echo mv -- "$entry" "${base}_${suffix}.${ext}"
  else
    echo mv -- "$entry" "${base}_${suffix}"
  fi
done

Remove the echo when it looks right to you.

Test cases:

touch a.zip b.zip foo bar file.csv a.file.csv 'test case' 'test case.csv'
mkdir baz

Sample output:

mv -- a.file.csv a.file_yoursuffix.csv
mv -- bar bar_yoursuffix
mv -- file.csv file_yoursuffix.csv
mv -- foo foo_yoursuffix
mv -- scr scr_yoursuffix
mv -- test case test case_yoursuffix
mv -- test case.csv test case_yoursuffix.csv

... skipping the directory baz, but renaming foo and bar appropriately.

1
 find <pathtodirectory> ! -name *.zip | awk -v suff=$suffix -F\. '{ print $1"_"suff"."$2 }'

You can use find to list the files and then parse the output through awk. Once you are happy with the list of files with the new suffix included, you can then run the command through awk again utilising awk's system function to create and command and execute it

find <pathtodirectory> ! -name *.zip | awk -v suff=$suffix -F\. '{ system("mv "$0" "$1"_"suff"."$2) }'

Note that there are command injection risks with the solution though. Note also that the solution relies on all the files having the structure filename.extension which may or may not be the case.

-1
for f in `ls -I "*.zip" `; do mv $f `echo $f | cut -d '.' -f1`_suffix.`echo $f | cut -d '.' -f2` ; done ;

Other interesting tools include utilities like rename

This is an interesting question, but this also means it has probably already been asked a lot of times. Note I kept the op's ls, which doesn't exclude directories.

  • 1
    This will fail for file names that contain IFS chars. – don_crissti Oct 5 '17 at 14:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.