6

I am trying to create 50 directories (dir-01..dir-50). And I want to create 50 files (01.txt..50.txt) inside each of the 50 directories.

For example:
    dir-01/01.txt..50.txt
    dir-02/02.txt..50.txt
    etc...

I am able to create the the directories, but I am having trouble with creating the files inside each. I am also trying to compress all these afterwards into a tar file.

This is where I am at so far:

for i in {1..50};
   do mkdir dir-$i;
done;
for j in {1..50};
   do touch $j.txt.dir-*;
done;
tar -cf final.tar dir-{1..50}

I know that second loop is wrong, but I am unsure how to proceed. Any advice is appreciated.

This seems to work, but I am unsure if it is correct in syntax or format:

for i in {1..50}; do
  mkdir "dir-$i";
  for j in {1..50}; do
      touch "./dir-$i/$j.txt";
    done;
done;

tar -cf final.tar dir-{1..50}
  • Do you want to create 50 files total, or 50 files in each directory? Please do not respond in comments; edit your question to make it clearer and more complete. – Scott Sep 29 '17 at 1:43
  • 1
    Your updated code is okay, except if you want dir-01/01.txt and so on, you have to use {01..50}. Btw, you don't need a loop at all: mkdir dir-{.1..50} && touch dir-{01..50}/{00..50}.txt would do the job as well. If you are trying to build a tar bomb, the files should not be empty though. (-; – Philippos Sep 29 '17 at 5:43
25

With zsh or bash or yash -o braceexpand:

$ mkdir dir-{01..50}
$ touch dir-{01..50}/file{01..50}.txt
$ ls dir-45
file01.txt file09.txt file17.txt file25.txt file33.txt file41.txt file49.txt
file02.txt file10.txt file18.txt file26.txt file34.txt file42.txt file50.txt
file03.txt file11.txt file19.txt file27.txt file35.txt file43.txt
file04.txt file12.txt file20.txt file28.txt file36.txt file44.txt
file05.txt file13.txt file21.txt file29.txt file37.txt file45.txt
file06.txt file14.txt file22.txt file30.txt file38.txt file46.txt
file07.txt file15.txt file23.txt file31.txt file39.txt file47.txt
file08.txt file16.txt file24.txt file32.txt file40.txt file48.txt

$ tar -cf archive.tar dir-{01..50}

With ksh93:

$ mkdir dir-{01..50%02d}
$ touch dir-{01..50%02d}/file{01..50%02d}.txt
$ tar -cf archive.tar dir-{01..50%02d}

The ksh93 brace expansion takes a printf()-style format string that can be used to create the zero-filled numbers.


With a POSIX sh:

i=0    
while [ "$(( i += 1 ))" -le 50 ]; do
    zi=$( printf '%02d' "$i" )
    mkdir "dir-$zi"

    j=0
    while [ "$(( j += 1 ))" -le 50 ]; do
        zj=$( printf '%02d' "$j" )
        touch "dir-$zi/file$zj.txt"
    done
done

tar -cf archive.tar dir-*  # assuming only the folders we just created exists

An alternative for just creating your tar archive without creating so many files, in bash:

mkdir dir-01
touch dir-01/file{01..50}.txt
tar -cf archive.tar dir-01

for i in {02..50}; do
    mv "dir-$(( i - 1 ))" "dir-$i"
    tar -uf archive.tar "dir-$i"
done

This just creates one of the directories and adds it to the archive. Since all files in all 50 directories are identical in name and contents, it then renames the directory and appends it to the archive in successive iterations to add the other 49 directories.

  • 2
    Simplicity as it's best. Love the touch dir-{1..50}/file{00..50}.txt part – muhammad Sep 29 '17 at 7:22
6

POSIXly, and to avoid problems when that list of 50 x 50 file path potentially exceeding the limit on the size of args+env:

awk 'BEGIN{for (i = 1; i <= 50; i++) printf "dir-%02d\n", i}' |
  xargs mkdir &&

  awk 'BEGIN{for (i = 1; i <= 50; i++)
              for (j = 1; j <= 50; j++)
                printf "dir-%02d/file-%02d.txt\n", i, j}' |
    xargs touch

With zsh (where {01..50} comes from), you can work around the arg list too long with zargs:

autoload zargs
mkdir dir-{01..50}
zargs dir-{01..50}/file-{01..50}.txt -- touch

You can also make dir{02..50} symlinks to dir-01 and use the h tar option:

mkdir dir-01
touch dir-01/file-{01..05}
for d in dir-{02..05}; do ln -s dir-01 "$f"; done
tar zchf file.tar.gz dir-{01..50}
  • +1 for showing us what we'd have to suffer through without fancy shells (or loops that invoke touch with only 50 args instead of the max). – Peter Cordes Sep 29 '17 at 13:36
5

For each directory, create 50 files:

for i in {1..50}; do
    mkdir dir$i
    touch dir$i/file{1..50}
done

And to do the counting, ls -l dir*/* | nl = 2500

4

The answer was easier than you thought:

for i in {1..50}; do
  mkdir "dir-$i"
  touch "./dir-$i/dir-$i.txt"
done

tar -cf final.tar dir-{1..50}
  • Awesome, thanks! I am still having trouble creating the 50 files in each directory. This gives me 100 total items (50 directories and 50 files). Is there a way to create 50 files in each directory based on $i? I am going to play with this and thanks again – Babeeshka Sep 29 '17 at 1:55
  • You'll need a double loop to do it that way. – Kusalananda Sep 29 '17 at 6:33
4

I believe this would work as you wanted.

for i in {1..50}; do mkdir $i.dir; cd $i.dir ; touch {1..50}.txt; cd ..; done; tar -cf dirs.tar *.dir; find . -type f | wc -l

Explanation:


  1. Here in first loop bash will create a dir with 1.dir name [ mkdir $i.dir ].

  2. Then it will enter in the 1.dir directory [ cd $i.dir ]

  3. Then it will create 50 files. To create 50 files I have used the brace expansion functionality of bash (as you have used in for) with touch [ touch {1..50}.txt ].

  4. Then It will return it's current directory [ cd .. ] and start the loop work.

  5. After finishing loop work it will tar the directories with .dir at the end of the name. [ tar -cf dirs.tar *.dir ].

  6. Then I have used find command [ find -type f | wc -l ] to show how much files were created. You will see 2551 files ("50*50=2550 +1" - extra 1 is your .tar file).

You can use this directly on the command line or you also can use it as a script.

1

You didn't specify the working environment, and although it seems to be bash/GNU I thought the following solution was sufficiently interesting to throw into the melting pot. It uses the BSD version of tar(1) (usually available as bsdtar on non-BSD platforms), and uses its @<mtree> feature to provide the archive content specification in mtree format.

awk -v tmpdir=`mktemp -d` -v tmpfile=`mktemp` -v uid=`id -u` -v gid=`id -g` 'BEGIN {print "#mtree"; print "/set uid=" uid " gid=" gid " mode=755";  for(d=1;d<=50;d++) {printf "dir-%02d type=dir content=%s\n", d, tmpdir; for(f=1;f<=50;f++) printf "%02d.txt type=file content=%s\n", f, tmpfile; print "..";}}' | tar cf final.tar @-

Split up for easier reading:

awk -v tmpdir=`mktemp -d` -v tmpfile=`mktemp` -v uid=`id -u` -v gid=`id -g` \
    'BEGIN {print "#mtree"; print "/set uid=" uid " gid=" gid " mode=755"; \
    for(d=1;d<=50;d++) {printf "dir-%02d type=dir content=%s\n", d, tmpdir; \
        for(f=1;f<=50;f++) printf "%02d.txt type=file content=%s\n", f, tmpfile; \
        print "..";}}' \
| tar cf final.tar @-

Explanation

  • Uses the mktemp(1) utility to (safely) create empty source directory and source file
  • The /set mtree directive defines the default uid/gid/mode
  • The content= attribute specifies the source content for each dir/file as the temp copy
  • @- reads the mtree spec from stdin
  • Leaves the mktemp files for the system to clean up (since, by default, they're created in the system "temp" directory - normally $TMPDIR or /tmp)

Remove the trailing | tar ... to see the mtree spec that is generated.

Of course, substitute bsdtar for tar as needed.

The key features of this approach are using mktemp to safely create the temporary file and directory (which avoids having to worry about conflicts with existing files and finding a safe/suitable location to create them) and only having to create one of each for any number of directories or files.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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