I have this script



for filePng in $folder/*
touch $filePng.txt

It works, just that, for a file named 001.png, it creates 001.png.txt instead of 001.txt.

How can I modify this?

  • 4
    It's a good habit to get into to quote your variables. Shell script is a weird language that evolved over time rather than being perfectly designed from the get-go, so unfortunately some annoying things like this become necessary. Without quoting your variables, whitespace or asterisks in the variable contents will cause things to break in weird ways. To make your scripts more robust, always surround uses of your variables with double quotes. Here, you would say for filePng in "$folder"/* and touch "$filePng".txt - note you only quote them when preceeded by a $.
    – Muzer
    Apr 11, 2017 at 17:28
  • 3
    This looks like an XY problem… Why are you trying to do this?
    – jeromej
    Apr 12, 2017 at 0:15

5 Answers 5


You can remove the existing extension using the shell's parameter expansion features

${parameter%pattern} The 'pattern' is matched against the end of 'parameter'. The result is the expanded value of 'parameter' with the shortest match deleted.

So in your case, replace $filePng.txt with "${filePng%.png}.txt"


You can use the basename command here:

touch "$folder/$(basename "$filePng" .png).txt"

Note the additional $folder/. This is neccessary since the basename command removes the path from.

  • Could I suggest that you quoted your parameter expansion and command substitution?
    – Tom Fenech
    Apr 12, 2017 at 8:37
  • @TomFenech yeah, probably a good idea to quote the whole string. I edited my answer.
    – Wayne_Yux
    Apr 12, 2017 at 13:31
  • I'm not sure why you removed the inner quotes around $filePng - they were useful too.
    – Tom Fenech
    Apr 12, 2017 at 13:40
  • 1
    Nope, because $( ) establishes a new quoting context.
    – Tom Fenech
    Apr 12, 2017 at 14:43
  • 2
    Oh, you are right - learned something new today ;-)
    – Wayne_Yux
    Apr 12, 2017 at 14:57

With variation on what steeldriver already mentioned - parameter expansion - we can use string replacement to do the job. Additionally, you should quote variables. Below is your edited script.



for filePng in "$folder"/*
    touch "${filePng/.png/.txt}"

If you have a lot of files to create it would be worthwhile to “touch” more than one file at a time, so that you don't need to fork a new process for each of them (which takes quite some time if performed multiple thousand times).

Option 1: pattern substitution + xargs

This option will supply multiple paths to the touch command at once, usually a few thousand or whatever the system can fit on a single command line.

find "$folder" -mindepth 1 -maxdepth 1 -name '*.png' -print0 |
sed -ze 's/\.png$/.txt/' |
xargs -r0 -- touch --

Option 2: parameter expansion + command output redirection

This option doesn't run touch at all but uses Bash/Bourne/POSIX shell features instead which don't require sub-processes at all.

for f in "$folder"/*.png; do
    : >> "${f%.png}.txt"

If you're confident that you don't have files with .png somewhere in the middle of the name, then you can just use an array with parameter expansion:

pngs=( /path/to/pngs/*.png )
touch "${pngs[@]/.png/.txt}"

This stores all the paths to the files ending in .png in an array and then uses parameter expansion to create the list of .txt files, by substituting .png for .txt on each one.

Bear in mind that this will break if you have so many files that they cannot all be passed as arguments to the same invocation of touch.

You must log in to answer this question.

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