8

I have a text file which contains a list of items (one for each line) and I need to create, in a given directory, a file for every entry in the said list; each one of these files should be named with a string in the said list. Also, if possible, after the string contained in the text file, a custom text should be appended to the filenames.

Which commands should I use to do that from the command line? I suppose that cat, grep and touch (and maybe xargs?) should be involved in the process, but after a few trials I still can't find the correct syntax.

Can someone enlighten me about this?

2 Answers 2

12

Say you have list.txt like this:

file 1
file 2
file 3

You can achieve what you want with:

while read FILE; do touch "${FILE} some string"; done < list.txt
4
  • Why ... < list.txt rather than cat list.txt | ...? Granted the latter spawns a cat process which the former doesn't, but it also behaves a lot more predictably, and is more legible besides... May 1, 2013 at 21:48
  • 1
    Redirection is integrated in Bash, there's no need to use cat here. If you prefer you can write the redirection before the command: < list.txt while .... Can you elaborate "but it also behaves a lot more predictably"?
    – cYrus
    May 1, 2013 at 21:55
  • ISTR having trouble in the past where < file or < `command` didn't actually produce anything on standard input of the command at which it was aimed, to the point where I gave up trying to get it to work at all -- hence my defaulting to cat file | in my answer. That was a while ago, though, and it could just be I didn't know what I was doing; I should probably try it again. May 1, 2013 at 21:58
  • 1
    That said, < list.txt while read FILE...done still just looks wrong. I understand that it isn't, but it looks like it should be. :) May 1, 2013 at 21:59
9

So you've got a file list which contains:

this
that
the-other

and you want to create, for each line in that file, a file with the name given on that line. That's pretty easy – if and only if each line consists just of a single word:

cat list | xargs touch --

(The -- tells touch not to try to parse anything after it as an argument; without it, if you have filenames like -this, the leading - will confuse touch into thinking 't', 'h', 'i', and 's' are meant as arguments, rather than parts of the literal filename to touch.)

That's the simple case taken care of, but it's not what you asked; if you want the filename to contain some text beyond what's specified on the lines in list, things get more complicated; xargs can't do that sort of interpolation. Instead, you'd need something like the following (which requires that your shell is bash, as is standard on most systems):

for file in `cat list`; do touch -- $file-customtext; done

Beware that this still only works if each line consists of a single word only. In this command, $file is replaced by the filename specified on a given line in list; everything around it is taken literally, so this command with the list contents above would create the following files:

this-customtext
that-customtext
the-other-customtext

You can replace '-customtext' with whatever you like, of course, and you can also have a prefix as well as a suffix; if you instead had do touch -- some-$file-text, for example, it'd work just as well, and result in files named some-this-text &c.

Now, then, supposing the contents of list aren't so simple -- specifically, that they contain spaces, non-word characters, or other things that the shell won't interpret properly. If you ask three people what they'd recommend in this case, you'd likely get five answers; I'm a Perl hacker, so my answer is Perl. Specifically, let's assume a list that contains:

This is the first line
of three (three?) lines
in the list file.

The way I'd do this would be:

cat list | perl -ne'chomp; system(qq|touch -- "custom $_ text"|)'

Which produces the resulting files:

custom This is the first line text
custom of three (three?) lines text
custom in the list file. text

The custom text in this case can be whatever you like, spaces and all, so long as it contains neither " (which will confuse touch) nor ' (which will confuse perl); in the Perl command, $_ represents the contents of a given line from list, and custom text belongs either before or after it, but within the double quotes. (Custom text outside the double quotes will be regarded by touch as a separate filename, which will be literally created unless it contains something that'll cause touch to error out.)

17
  • 1
    Nope! Apparently when you for across lines in bash, it takes any whitespace as a line separator, instead of just \n. I knew bash was weird and picky, but...In any case, I've updated the answer to handle this case by means of a little command-line Perl. May 1, 2013 at 21:44
  • 1
    Also, just FYI, enclosing a string in backticks (`), most places where you can do that, will cause the string to be taken as a command whose results are interpolated in its place, as in the for construct in my answer. May 1, 2013 at 21:46
  • 1
    Please read: Why you shouldn't read lines with for – there's no need for perl workarounds or cating really, you just need to use a while read construct instead. Bash isn't being picky, it just treats whitespace as an argument separator.
    – slhck
    May 1, 2013 at 21:47
  • 1
    Bash takes a while getting used to, but there's often a standard way to do things which you'll have to learn once and then apply everywhere :) The Bash FAQ covers almost any case though.
    – slhck
    May 1, 2013 at 21:51
  • 1
    @Sekhemty I would really recommend against using such a workaround. Bash can treat files with spaces in their name just fine, you just have to use the proper commands (see cYrus' answer below). Many problems people have with Bash stem from "wrong" commands they found like for loops to iterate on files. Those work fine for many cases but break unexpectedly for others.
    – slhck
    May 1, 2013 at 22:01

You must log in to answer this question.

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