{"id":14277,"date":"2026-06-09T01:00:00","date_gmt":"2026-06-09T05:00:00","guid":{"rendered":"https:\/\/www.both.org\/?p=14277"},"modified":"2026-06-04T17:39:56","modified_gmt":"2026-06-04T21:39:56","slug":"learn-by-writing-test-programs","status":"publish","type":"post","link":"http:\/\/www.both.org\/?p=14277","title":{"rendered":"Learn by writing test programs"},"content":{"rendered":"<div class=\"pld-like-dislike-wrap pld-template-1\">\r\n    <div class=\"pld-like-wrap  pld-common-wrap\">\r\n    <a href=\"javascript:void(0)\" class=\"pld-like-trigger pld-like-dislike-trigger  \" title=\"\" data-post-id=\"14277\" data-trigger-type=\"like\" data-restriction=\"cookie\" data-already-liked=\"0\">\r\n                        <i class=\"fas fa-thumbs-up\"><\/i>\r\n                <\/a>\r\n    <span class=\"pld-like-count-wrap pld-count-wrap\">    <\/span>\r\n<\/div><\/div>\n<p class=\"wp-block-paragraph\">After writing about how <a href=\"https:\/\/www.both.org\/?p=14209\">DOS filenames can have spaces<\/a> and a follow-up article about <a href=\"https:\/\/www.both.org\/?p=14211\">where those spaces can be<\/a>, I&#8217;ve received a few emails asking which characters are allowed in DOS filenames, and which are not. Wikipedia has an excellent discussion about the DOS <a href=\"https:\/\/en.wikipedia.org\/wiki\/Design_of_the_FAT_file_system#Directory_table\">directory table<\/a> which includes the legal characters for DOS filenames, but this is an opportunity to learn a bit about programming.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A <em>power multiplier<\/em> in open source is that if you have a compiler and know a bit about programming, you can learn all kinds of things about how computer work. And in this case, we can learn about what characters are allowed in DOS filenames by writing a test program.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Test all characters<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">DOS filnames can actually contain so-called &#8220;extended ASCII&#8221; characters. Really, the ASCII standard only defines characters from 0 to 127, and any character beyond that is a special character. DOS uses code points from 128 to 255 in a <em>code page<\/em>, such as code page 437 (the typical &#8220;extended ASCII&#8221; characters that includes line-drawing characters, Greek characters, and other items). However, US keyboards cannot easily type those extra characters, so let&#8217;s make a test program that creates filenames using characters <em>up to but not including<\/em> 127. Code point 127 is actually the &#8220;delete&#8221; character, and isn&#8217;t actually allowed in DOS filenames anyway.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s write a short test program that iterates through the printable characters in a <code>for<\/code> loop:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;stdio.h&gt;\n\nint main()\n{\n    char x;\n\n    for (x=' '+1; x&lt;127; x++) {\n        testfile(x);\n    }\n\n    return 0;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This starts the loop at the character <em>after<\/em> the space, which is the <code>!<\/code> character. I wrote it as <strong>space + 1<\/strong> because we already know that DOS filenames <a href=\"https:\/\/www.both.org\/?p=14211\">cannot have a trailing space<\/a> so there&#8217;s no point in testing a space in a filename; we already know that answer. The loop continues <em>as long as<\/em> the character is <em>up to but not including<\/em> 127.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For each character in the loop, the program calls the <code>testfile<\/code> function. That&#8217;s the function where we&#8217;ll do the actual test.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Save a test file<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>testfile<\/code> function uses the character to create a filename, then opens a file, writes some data to it, and closes it. To make these test files easy to delete later, I&#8217;ll name them all with a <code>.TST<\/code> file extension.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>int testfile(char a)\n{\n    char filename&#91;] = \"_.TST\";<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ve defined a string called <code>filename<\/code> with the initial value <code>_.TST<\/code>. The first character in the string is actually a placeholder; I&#8217;ll replace it with the <strong>a<\/strong> character before I try to open a new file with that name:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    filename&#91;0] = a;\n    p = fopen(filename, \"w\");<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">With that understanding of how the <code>testfile<\/code> function works, let&#8217;s write the full function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>int testfile(char a)\n{\n    char filename&#91;] = \"_.TST\";\n    FILE *p;\n\n    fputc(a, stdout);\n    fputs(\" - \", stdout);\n\n    filename&#91;0] = a;\n    p = fopen(filename, \"w\");\n\n    if (p==NULL) {\n        puts(\"fail\");\n        return 0; \/* fail *\/\n    }\n\n    fputc(a, p);\n    fclose(p);\n\n    puts(\"Ok\");\n    return 1;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the function cannot open a new file with that filename, it prints a &#8220;fail&#8221; message, then immediately exits. Otherwise, it writes one character to the file, then closes it. If successful, the function also prints an &#8220;Ok&#8221; message.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Putting it all together<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This program is not very long, at about 40 lines to test each character in a DOS filename. And that&#8217;s an important goal when you&#8217;re trying to learn something: <em>write a simple program to test one thing<\/em>. In this case, the program only tests characters in DOS filenames. Here&#8217;s the full program:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;stdio.h&gt;\n\nint testfile(char a)\n{\n    char filename&#91;] = \"_.TST\";\n    FILE *p;\n\n    fputc(a, stdout);\n    fputs(\" - \", stdout);\n\n    filename&#91;0] = a;\n    p = fopen(filename, \"w\");\n\n    if (p==NULL) {\n        puts(\"fail\");\n        return 0; \/* fail *\/\n    }\n\n    fputc(a, p);\n    fclose(p);\n\n    puts(\"Ok\");\n    return 1;\n}\n\nint main()\n{\n    char x;\n\n    for (x=' '+1; x&lt;127; x++) {\n        testfile(x);\n    }\n\n    return 0;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ve saved this as <strong>names.c<\/strong> and compiled it using a C compiler. You should be able to use any ANSI-compatible C compiler; for example, we include Open Watcom in the FreeDOS distribution:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>D:\\SRC\\NAMES&gt; wcl -q names.c<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the program<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">After we compile the program, we can use it to test which characters are allowed in a DOS filename. To make it easy to clean up after myself, I ran the program in a new <strong>TST<\/strong> directory; that way, I can just delete the directory later:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>D:\\SRC\\NAMES&gt;mkdir tst\nD:\\SRC\\NAMES&gt;cd tst\nD:\\SRC\\NAMES\\TST&gt;..\\names &gt; ..\\names.txt<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The program will loop through all characters from <code>!<\/code> (33) to <code>~<\/code> (126). For each character, the program tries to write a new file with a name like <code>_.TST<\/code>. I&#8217;ve saved the output to a file called <code>names.txt<\/code>, but you can just list the directory to see what files are there; these are the valid characters in a DOS filename:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>D:\\SRC\\NAMES\\TST&gt;dir \/w \/l \/b\n&#91;.]            &#91;..]           !.tst          #.tst          $.tst\n%.tst          &amp;.tst          '.tst          (.tst          ).tst\n-.tst          0.tst          1.tst          2.tst          3.tst\n4.tst          5.tst          6.tst          7.tst          8.tst\n9.tst          @.tst          a.tst          b.tst          c.tst\nd.tst          e.tst          f.tst          g.tst          h.tst\ni.tst          j.tst          k.tst          l.tst          m.tst\nn.tst          o.tst          p.tst          q.tst          r.tst\ns.tst          t.tst          u.tst          v.tst          w.tst\nx.tst          y.tst          z.tst          ^.tst          _.tst\n`.tst          {.tst          }.tst          ~.tst<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Note that DOS filenames are case insensitive, so <code>A.TST<\/code> is the same as <code>A.TST<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With this directory list, we can see that the valid &#8220;plain ASCII&#8221; characters for DOS filenames are the letters A to Z, the numbers 0 to 9, and these special characters: ! # $ % &amp; &#8216; ( ) &#8211; @ ^ _ ` { } ~ (and space, with the caveat that you cannot have trailing spaces).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the &#8220;fail&#8221; entries in the <code>names.txt<\/code> file, we can see the list of characters that are <em>not<\/em> allowed in regular DOS filenames:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>D:\\SRC\\NAMES\\TST&gt;find \"fail\" ..\\names.txt\n---------------- NAMES.TXT\n\" - fail\n* - fail\n+ - fail\n, - fail\n. - fail\n\/ - fail\n: - fail\n; - fail\n&lt; - fail\n= - fail\n&gt; - fail\n? - fail\n&#91; - fail\n\\ - fail\n] - fail\n| - fail<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Learn by writing test programs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With a little knowledge of programming, you can learn all kinds of things about how computers work. With this simple test program, we were able to demonstrate what characters are (and are not) allowed in DOS filenames.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With a little knowledge of programming, you can learn all kinds of things about how computers work<\/p>\n","protected":false},"author":33,"featured_media":2949,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_lmt_disableupdate":"","_lmt_disable":"","footnotes":""},"categories":[340,158,150],"tags":[267,108,152],"class_list":["post-14277","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-freedos","category-open-source","category-programming","tag-freedos","tag-open-source","tag-programming"],"modified_by":"David Both","_links":{"self":[{"href":"http:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/14277","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/users\/33"}],"replies":[{"embeddable":true,"href":"http:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=14277"}],"version-history":[{"count":1,"href":"http:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/14277\/revisions"}],"predecessor-version":[{"id":14278,"href":"http:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/14277\/revisions\/14278"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/media\/2949"}],"wp:attachment":[{"href":"http:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=14277"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=14277"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=14277"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}