12

I have a file containing a list of directories. For instance

/foo/bar/dir1
/foo/bar/dir2
/foo/bar/dir3

I want to create all these directories. Here's what I did:

for dir in $(cat myfile); do mkdir $dir; done

What would be the correct way of doing this while avoiding the "useless use of cat"?

Ideally, answers would focus on Ksh88, but I'm also interested in other shells

rahmu
  • 20,023

7 Answers7

13

AProgrammer's suggestion of using xargs is often best, but another option is to use redirection into a while loop, which allows additional commands to be made and variables to be set:

while read -r dir; do mkdir $dir; done < myfile

An example of a more complicated structure would be:

now=`date +%Y%m%d.%H%M%S`
while read -r dir; do
    newdistfile="/tmp/dist-`echo $dir | tr / _`.tgz"
    mv $dir ~/backups/$dir.$now &&
        mkdir $dir &&
        tar xzfC $newdistfile $dir
done < myfile

This is not something that xargs could do without writing a 'helper program'.

Kevin
  • 40,767
Arcege
  • 22,536
  • What are the limits of using xargs over read? It is my understanding that xargs will fork a new process (unlike read that is a shell builtin). Isn't this something you want to avoid? – rahmu Dec 19 '11 at 16:40
  • xargs will operate on a single command, so if you want to have a compound statement (like more than one, an if, while or for, etc.) or setting a variable, then you cannot use xargs. Yes, xargs will fork one new statement, plus one for some number of input lines, which is very little overhead. However, some older versions of xargs do not take a -0 option, so a while read will be the way to go if there may be whitespace in the input directory names. – Arcege Dec 19 '11 at 17:38
12

I'd do something like

xargs mkdir < myfile
AProgrammer
  • 2,318
12

At least in bash, as long as there are no filenames containing spaces and newlines, this:

mkdir $(< myfile) 

works. So we have a useless use of for, xargs too.

< does not start a new process in bash, in contrast to cat, but I don't know for ksh.

user unknown
  • 10,482
4

This is the solution closer to your original script still avoiding the useless cat

for dir in $(<myfile); do mkdir $dir; done

It still contains a useless loop though.

jlliagre
  • 61,204
3

I wouldn't say that's a useless use of cat.

In the classic sense, a UUOC means writing:

cat file | some_command and its args ...

instead of the equivalent and cheaper

<file some_command and its args ...

or (equivalently and more classically)

some_command and its args ... < file

In this case:

for dir in $(cat myfile); do mkdir $dir; done

there is no command into which the output of cat is being piped.

There are certainly alternatives that don't use cat. In particular, for bash and ksh $(<myfile) is likely to be faster than $(cat myfile), though not all shells support that construct. But I wouldn't call the original code a "useless use of cat" any more than I'd call AProgrammer's solution a "useless use of xargs".

1

With bash 4 and limited by ARG_MAX

mapfile -t <file && mkdir -- "${MAPFILE[@]}"
iruvar
  • 16,725
0

Yours is a perfect example of intended POSIX usage of a while-loop, specifically what I call a "While Redirect".

"While Redirect"

while read dir
do
  mkdir $dir
done < myfile

Now, compare the previous example to what I call a "While Pipe":

"While Pipe"

cat myfile | while read dir
do
  mkdir $dir
done

The cat is not an UUoC in the While Pipe; it is part of the command syntax. Just like you might have used grep -E . myfile | while or something else.

It is considered Useless in the for-loop mostly because it --in conjunction with the backquotes-- is considered to be inefficient and dangerous there.

See here for more details

Kajukenbo
  • 307