12

I have this script

#!/bin/bash

folder='/home/data/mnist/training'

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

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 '17 at 17:28
  • 3
    This looks like an XY problem… Why are you trying to do this? – JeromeJ Apr 12 '17 at 0:15
16

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 '17 at 8:37
  • @TomFenech yeah, probably a good idea to quote the whole string. I edited my answer. – Wayne_Yux Apr 12 '17 at 13:31
  • I'm not sure why you removed the inner quotes around $filePng - they were useful too. – Tom Fenech Apr 12 '17 at 13:40
  • 1
    Nope, because $( ) establishes a new quoting context. – Tom Fenech Apr 12 '17 at 14:43
  • 2
    Oh, you are right - learned something new today ;-) – Wayne_Yux Apr 12 '17 at 14:57
32

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"

10

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.

#!/bin/bash

folder='/home/data/mnist/training'

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

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"
done
4

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.

protected by souravc Apr 13 '17 at 5:44

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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