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?

2
  • note, ls will also print folder names Commented Oct 5, 2017 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. Commented Oct 5, 2017 at 14:37

5 Answers 5

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

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 
3
  • You all know more than I do but this works fine for me. Has solid condition tests and is concise. Thanks. Commented Oct 5, 2017 at 14:48
  • About embedding {} in shell code: unix.stackexchange.com/a/156010/116858 Commented Oct 5, 2017 at 14:49
  • @RomanPerekhrest Because you're not passing any arguments to the bash -c shell. Try adding find-bash {} to the end before \;. Commented Oct 5, 2017 at 15:06
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.

0
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
  • 1
    This will fail for file names that contain IFS chars. Commented Oct 5, 2017 at 14:13

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.