This may be a duplicate of Duplicate file x times in command shell and is definitely a duplicate of How to duplicate a file a number of times while embedding an index in each file but the person who posted the answer was last seen in 2017 and I'd like to know how I can use this as a function in zsh so that I can call it on a file with any extension (not just txt files) like so: cpx file.ext n where n is the number of copies to make. Also, how I can separate the filename and file extension.

This was the answer for txt files only:


orig=ascdrg3.txt # start with this file

count=1 #loop variable
max=5   #number of files to create
while test "$count" -le "$max" ; do
    # Remove extension
    base=$(basename "$in" .txt)

    # get the prefix
    prefix=$(expr substr "$base" 1 $((${#base}-1)))

    # get last letter
    last=$(expr substr "$base" ${#base} 1)

    while true ;
        # Advance letter, while the file doesn't exist
        last=$(echo "$last" | tr A-Z B-ZA)
        last=$(echo "$last" | tr a-z b-za)
        last=$(echo "$last" | tr 0-9 1-90)

        # construct new file name

        # continue if it doesn't exist
        # (otherwise, advance the last letter and try again)
        test -e "$new" || break

        test "$new" = "$orig" \
            && { echo "error: looped back to original file" >&2 ; exit 1; }

    # Create new file
    cp "$orig" "$new"

    # Modify first line of new file
    sed -i "1s/\$/number($count,$max)/" "$new"

    # Advance counter

    # loop again

Is there a much smaller way to do this?

What I want is: cpx hello.py 3 should create hello1.py hello2.py hello3.py

1 Answer 1


There's definitely a simpler way to do it robustly in zsh. There's a simpler way to do it robustly in plain sh, too: this script is overly complicated and fragile (assumes all file names have an extension, overwrites files without prompting, …). Since this thread is about zsh, I'll take advantage of zsh's capabilities.

The history and parameter expansion modifiers r and e are useful to split a file name between the base name and the extension. However, beware that they only work usefully when the file does have an extension.

Warning: untested code.

function cpx {
  if (($# != 2)); then
    cat >&2 <<EOF
Usage: cpx FILENAME N
Make N copies of FILENAME.
    return 1
  local n=$2
  if [[ $n != <-> ]]; then
    print -ru2 "cpx: $n: not a number"
    return 1
  local prefix=$1 suffix= i
  # If there is an extension, put the number before the extension
  if [[ $prefix:t == ?*.* ]]; then
  # If the part before the number ends with a digit, separate the additional
  # number with a dash.
  if [[ $prefix == *[0-9] ]]; then
  # Copy foo.bar to foo1.bar, foo2.bar, ...
  for ((i=1; i<=n; i++)); do
    cp -p -i -- $1 $prefix$i$suffix
  • I tested it with many differently named files (eg. file, file.txt, file.$1, file-2, file.3.ext, folder/, etc) and it didn't break so it works perfectly! Just when I used a math expression cpx file 5+1 then it printed cpx: 5+1: not a number but it still did the job successfully.
    – ntruter42
    Apr 3, 2021 at 22:47
  • What does the local prefix=$1 suffix= i do? I removed the i and just left local prefix=$1 suffix= and it still works. Is that i necessary?
    – ntruter42
    Apr 3, 2021 at 23:03
  • 2
    @ntruter42 Without local, the variables will remain defined after the function is executed. If you want to be able to perform arithmetic expressions, you can use typeset -i i instead of local i. Apr 3, 2021 at 23:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .