diff options
| author | B. Watson <urchlay@slackware.uk> | 2025-11-14 04:22:39 -0500 |
|---|---|---|
| committer | B. Watson <urchlay@slackware.uk> | 2025-11-14 04:22:39 -0500 |
| commit | e286ed23ae3cabfb75327d8512dc937b2ecf9be1 (patch) | |
| tree | a39a2494cdcca3145f283a515bfa42eb5f33e0df | |
| parent | e2da2bffe58a76c091d3496bd3ca2d2f18ea2eb6 (diff) | |
| download | unalf-e286ed23ae3cabfb75327d8512dc937b2ecf9be1.tar.gz | |
Add command-line options.
| -rw-r--r-- | TODO.txt | 18 | ||||
| -rw-r--r-- | src/Makefile | 6 | ||||
| -rw-r--r-- | src/extract.c | 75 | ||||
| -rw-r--r-- | src/glob.c | 120 | ||||
| -rw-r--r-- | src/io.c | 16 | ||||
| -rw-r--r-- | src/listalf.c | 52 | ||||
| -rw-r--r-- | src/opts.c | 65 | ||||
| -rw-r--r-- | src/unalf.1 | 103 | ||||
| -rw-r--r-- | src/unalf.c | 60 | ||||
| -rw-r--r-- | src/unalf.h | 35 | ||||
| -rw-r--r-- | src/unalf.rst | 100 |
11 files changed, 533 insertions, 117 deletions
@@ -1,19 +1,5 @@ -- selective extraction, possibly with wildcards - -- rename old file to flle~ rather than overwriting - -- implement a few unzip/arc style options: - -a = convert text files (Atari EOL => UNIX NL), -aa = all files - -e = extract (already the default action) - -t = test checksums - -k = keep trailing dot for extensionless filenames - -l = list (filenames only) - -L = lowercase filenames - -d <dir> = set output dir (create if needed) - -o = overwrite (do not create file~ backups) - -p = extract to stdout - -x <pattern> = do not extract files matching pattern - -v = verbose list +- be more informative with -t option ("No errors found", or "2 files + with bad checksums found"). - reverse-engineer the ALF14.COM binary, so we have a compressor. this would be more of "just to say I did it" thing, there's no real diff --git a/src/Makefile b/src/Makefile index 56951f2..f8b73cf 100644 --- a/src/Makefile +++ b/src/Makefile @@ -3,10 +3,14 @@ CFLAGS=-Wall -Wno-unused-label -I../f65 $(COPT) all: unalf unalf.1 alfsum alfsum.1 -unalf: unalf.o io.o listalf.o extract.o ../f65/f65.o +unalf: unalf.o io.o listalf.o extract.o ../f65/f65.o glob.o opts.o unalf.o: unalf.c unalf.h ../f65/f65.h +glob.o: glob.c unalf.h + +opts.o: opts.c unalf.h + io.o: io.c unalf.h addrs.h ../f65/f65.h listalf.o: listalf.c addrs.h unalf.h ../f65/f65.h diff --git a/src/extract.c b/src/extract.c index 079fe7f..4f761e4 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1,9 +1,14 @@ #include <stdio.h> #include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <limits.h> #include <f65.h> #include "unalf.h" #include "addrs.h" +char *out_filename; + void dpoke(int addr, u16 value) { mem[addr] = value & 0xff; mem[addr + 1] = value >> 8; @@ -13,29 +18,79 @@ u16 dpeek(int addr) { return mem[addr] | (mem[addr + 1] << 8); } -void extract_alf(void) { - char *filename; +void fix_filename(void) { + char *p; + + if(!out_filename) return; + + if(opts.lowercase) { + for(p = out_filename; *p; p++) + *p = tolower(*p); + } + + if(!opts.keepdot) { + for(p = out_filename; *p; p++) + if(p[0] == '.' && !p[1]) + *p = '\0'; + } +} + +void make_backup(void) { + char backup[PATH_MAX]; + + strcpy(backup, out_filename); + strcat(backup, "~"); + /* silently ignore errors! */ + rename(out_filename, backup); +} + +void extract_alf(void) { /* get ready to call fake 6502 stuff. set up memory like the Atari. */ dpoke(MEMTOP, 0xbc1f); while(read_alf_header()) { - filename = (char *)(mem + alf_hdr_filename); - printf("Uncrunching %s\n", filename); + out_filename = (char *)(mem + alf_hdr_filename); - if(!(out_file = fopen(filename, "wb"))) { - fprintf(stderr, "%s: ", self); - perror(filename); - exit(1); + fix_filename(); + + if(!file_wanted(out_filename)) { + if(fseek(in_file, getquad(alf_hdr_compsize0), SEEK_CUR) != 0) { + fputs(self, stderr); + perror(": fseek"); + exit(1); + } + out_filename = 0; + continue; + } + + if(!opts.quiet) { + printf("%s %s\n", opts.testonly ? "Testing" : "Uncrunching", out_filename); + } + + if(opts.extract_to_stdout) { + out_file = stdout; + } else { + if(opts.testonly) { + out_filename = "/dev/null"; + } else if(!opts.overwrite) { + make_backup(); + } + if(!(out_file = fopen(out_filename, "wb"))) { + perror(out_filename); + exit(1); + } } uncrunch_file(); - fclose(out_file); + + if(!opts.extract_to_stdout) fclose(out_file); + out_filename = 0; } } static void chksum_err(void) { - fprintf(stderr, "%s: checksum error on file %s\n", self, in_filename); + fprintf(stderr, "%s: checksum error on file %s\n", self, out_filename); } void uncrunch_file(void) { diff --git a/src/glob.c b/src/glob.c new file mode 100644 index 0000000..cb91b79 --- /dev/null +++ b/src/glob.c @@ -0,0 +1,120 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "unalf.h" + +/* this is an Atari DOS or MS-DOS style match: "*.*" matches everything, + even files with no extension (no dot). "*" by itself matches only + files with no extension, as does "*." + matching is case-insensitive. */ + +static void split_filename(const char *fname, char *name, const char **ext) { + int i; + + for(i = 0; i < 8; i++) { + if(fname[i] == '.' || fname[i] == '\0') + break; + name[i] = fname[i]; + } + name[i] = '\0'; + + *ext = strchr(fname, '.'); + if(*ext) { + (*ext)++; + if(!**ext) *ext = 0; + } + + // fprintf(stderr, "split %s into: '%s' and '%s'\n", + // fname, name, (*ext ? *ext : "<none>")); +} + +static int match_part(const char *pat, const char *arg) { + char p; + + if(arg && arg[0] == '\0') arg = 0; + + if(!pat) { + if(arg) + return 0; + else + return 1; + } + + while((p = toupper(*pat++))) { + // fprintf(stderr, "p == %c\n", p); + switch(p) { + case '*': return 1; + case '?': + if(!arg || *arg == '\0') + return 0; + arg++; + break; + default: + if(!arg || toupper(*arg) != p) + return 0; + arg++; + break; + } + } + + return *arg ? 0 : 1; +} + +int globmatch(const char *pat, const char *arg) { + char npat[9], name[9]; + const char *epat = 0, *ext = 0; + + split_filename(pat, npat, &epat); + split_filename(arg, name, &ext); + + return match_part(npat, name) && match_part(epat, ext); +} + +int file_wanted(const char *filename) { + int i; + char **ig; + + /* if excluded, always return false */ + for(i = 0; i < exclude_count; i++) { + if(globmatch(exclude_globs[i], filename)) + return 0; + } + + /* if no include globs, always match (as though *.* were given) */ + if(!*include_globs) + return 1; + + ig = (char **)include_globs; + while(*ig) { + if(globmatch(*ig, filename)) + return 1; + ig++; + } + + return 0; +} + +/* for testing: */ +#if 0 +int main(int argc, char **argv) { + int matched; + const char *pattern; + + if(argc < 3) { + fprintf(stderr, "usage: %s <glob-pattern> <filename> ...\n", argv[0]); + return 1; + } + + pattern = argv[1]; + argv++; + + while(*++argv) { + matched = globmatch(pattern, *argv); + printf("%s %s %s\n", *argv, matched ? "matches" : "DOES NOT match", pattern); + } + + return 0; +} +#endif @@ -76,15 +76,29 @@ void readblock(void) { dpoke(buf_len_l, bytes); } +int is_text_file(char *fn) { + if(globmatch("*.txt", fn)) return 1; + if(globmatch("*.doc", fn)) return 1; + if(globmatch("*.lst", fn)) return 1; + return 0; +} + /* mirror of readblock() */ void writeblock(void) { - int bytes, len, bufadr; + int i, bytes, len, bufadr; u8 *buf; + extern char *out_filename; bufadr = dpeek(buf_adr_l); buf = mem + bufadr; len = dpeek(buf_len_l); + if(opts.txtconv) { + if(opts.txtconv > 1 || is_text_file(out_filename)) + for(i = 0; i < len; i++) + if(buf[i] == 0x9b) buf[i] = '\n'; + } + // fprintf(stderr, "writeblock, bufadr = $%04x, len = $%04x\n", bufadr, len); bytes = fwrite(buf, 1, len, out_file); diff --git a/src/listalf.c b/src/listalf.c index 051f2fe..71d8195 100644 --- a/src/listalf.c +++ b/src/listalf.c @@ -77,30 +77,40 @@ static int comp_percent(unsigned int orig, unsigned int comp) { void list_alf(void) { unsigned int c = 0, orig_size, comp_size, total_osize = 0, total_csize = 0; char buf[100]; - char *filename; + extern char *out_filename; - puts("Name Length Size now Comp Date Time CkSum"); - puts("============ ======== ======== ==== ========= ====== ====="); + if(opts.verbose_list) { + puts("Name Length Size now Comp Date Time CkSum"); + puts("============ ======== ======== ==== ========= ====== ====="); + } while(read_alf_header()) { c++; orig_size = getquad(alf_hdr_origsize0); comp_size = getquad(alf_hdr_compsize0); - filename = (char *)(mem + alf_hdr_filename); + out_filename = (char *)(mem + alf_hdr_filename); + + if(!opts.verbose_list) + fix_filename(); total_osize += orig_size; total_csize += comp_size; - printf("%-12s ", filename); - printf("%8d ", orig_size); - printf("%8d ", comp_size); - printf("%3d%% ", comp_percent(orig_size, comp_size)); - format_msdos_date(buf); - printf("%9s ", buf); - format_msdos_time(buf); - printf("%6s ", buf); - printf(" %04x", getword(alf_hdr_cksum_l)); - putchar('\n'); + if(opts.verbose_list) { + printf("%-12s ", out_filename); + printf("%8d ", orig_size); + printf("%8d ", comp_size); + printf("%3d%% ", comp_percent(orig_size, comp_size)); + format_msdos_date(buf); + printf("%9s ", buf); + format_msdos_time(buf); + printf("%6s ", buf); + printf(" %04x", getword(alf_hdr_cksum_l)); + putchar('\n'); + } else { + if(file_wanted(out_filename)) + printf("%s\n", out_filename); + } if(fseek(in_file, comp_size, SEEK_CUR) != 0) { fputs(self, stderr); @@ -109,10 +119,12 @@ void list_alf(void) { } } - fputs(" ==== ======== ======== ====\nTotal ", stdout); - printf("%4d ", c); - printf("%8d ", total_osize); - printf("%8d ", total_csize); - printf("%3d%% ", comp_percent(total_osize, total_csize)); - putchar('\n'); + if(opts.verbose_list) { + fputs(" ==== ======== ======== ====\nTotal ", stdout); + printf("%4d ", c); + printf("%8d ", total_osize); + printf("%8d ", total_csize); + printf("%3d%% ", comp_percent(total_osize, total_csize)); + putchar('\n'); + } } diff --git a/src/opts.c b/src/opts.c new file mode 100644 index 0000000..2a5628e --- /dev/null +++ b/src/opts.c @@ -0,0 +1,65 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include "unalf.h" + +#define OPTIONS "aeklLopqtvd:x:" + +/* uncomment to test exclude/include glob lists */ +// #define DEBUG_GLOBS + +static void add_exclude(const char *glob) { + if(exclude_count == MAX_EXCLUDES) + return; + exclude_globs[exclude_count++] = glob; +} + +#ifdef DEBUG_GLOBS +static void show_globs(void) { + int i; + + printf("Include globs:\n"); + while(*include_globs) + printf(" + %s\n", *include_globs++); + printf("Exclude globs:\n"); + for(i = 0; i < exclude_count; i++) + printf(" - %s\n", exclude_globs[i]); + exit(0); +} +#endif + +void parse_opts(int argc, char * const *argv) { + int opt; + + while((opt = getopt(argc, argv, OPTIONS)) != -1) { + switch(opt) { + case 'a': opts.txtconv++; break; + case 'e': opts.listonly = opts.testonly = 0; break; + case 'k': opts.keepdot++; break; + case 'l': opts.listonly++; opts.testonly = 0; break; + case 'L': opts.lowercase++; break; + case 'o': opts.overwrite++; break; + case 'p': opts.extract_to_stdout++; opts.quiet++; break; + case 'q': opts.quiet++; break; + case 't': opts.testonly++; opts.listonly = 0; break; + case 'v': opts.listonly = 1; opts.testonly = 0; opts.verbose_list++; break; + case 'd': opts.outdir = optarg; break; + case 'x': add_exclude(optarg); break; + default: + fprintf(stderr, "%s: invalid option (try -h or --help)\n", self); + exit(1); + } + } + + if(optind >= argc) { + fprintf(stderr, "%s: missing alf file argument (try -h or --help)\n", self); + exit(1); + } + + in_filename = argv[optind]; + include_globs = &argv[optind + 1]; /* might be null, that's OK */ + +#ifdef DEBUG_GLOBS + show_globs(); +#endif +} diff --git a/src/unalf.1 b/src/unalf.1 index 3ed769b..040a94e 100644 --- a/src/unalf.1 +++ b/src/unalf.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "UNALF" 1 "2025-11-12" "0.0.0" "Urchlay's Atari 8-bit Tools" +.TH "UNALF" 1 "2025-11-14" "0.0.0" "Urchlay's Atari 8-bit Tools" .SH NAME unalf \- extract Atari 8-bit ALF archives .\" RST source for unalf(1) man page. Convert with: @@ -36,7 +36,7 @@ unalf \- extract Atari 8-bit ALF archives . .SH SYNOPSIS .sp -unalf [\fB\-l\fP] \fBalf\-file\fP +unalf [\fB\-aetklLopqv\fP] [\fB\-d\fP \fIdir\fP] [\fB\-x\fP \fIwildcard\fP] \fIalf\-file\fP [\fIwildcard\fP ...] .SH DESCRIPTION .sp \fBunalf\fP lists or extracts the contents of an \fIALF\fP archive. @@ -45,30 +45,89 @@ unalf [\fB\-l\fP] \fBalf\-file\fP not compatible with it. It was used on the Atari 8\-bit platform beginning in the late 1980s. .sp -Extracted files are written to the current directory. Existing files -are overwritten without warning. -.SH OPTIONS +Extracted files are written to the current directory by +default. Existing files are backed up by default (by adding a \fB~\fP +suffix to the filename). .sp -For compatibility with \fBarc\fP(1) usage, the \fB\-\fP in front of -option letters is optional. +If one or more \fBwildcard\fP arguments are provided, files will only be +listed or extracted if they match one of the wildcards. See \fBWILDCARDS\fP +below for details. +.SH OPTIONS .INDENT 0.0 .TP -.B \-l -List contents of archive, but do not extract. The output format -is similar to that of \fBarc \-v\fP, minus the \fIStowage\fP column, since -\fIALF\fP doesn\(aqt support multiple compression types. The date and time -are displayed, but in most .alf files these are the default values -of "8 Jan 82 12:24a". +.B \-a +Convert text file line endings from Atari EOL to UNIX newline. Text +files are detected by name: \fB*.txt\fP, \fB*.doc\fP, \fB*.lst\fP files +are considered text. .TP -.B \-v -Same as \fB\-l\fP\&. -.UNINDENT -.INDENT 0.0 +.BI \-a\fB a +Convert line endings in all extracted files. This will corrupt any +executables or non\-text data files, so use with caution. .TP -.B \fB\-e\fP, \fB\-x\fP +.BI \-d \ output\-dir +Write extracted files to this directory, which will be created if it +does not exist. The default is the current directory. +.TP +.B \-e Extract files. This is actually a no\-op, as extraction is the default action. +.TP +.B \-k +Keep trailing periods. In most (all?) ALF files, filenames that have no +extension are stored with a trailing period, e.g. \(aq\fBFOO\fP\(aq would +be stored as \(aq\fBFOO.\fP\(aq. By default, \fBunalf\fP removes the trailing +period; use \fB\-k\fP to keep it. +.TP +.B \-l +List filenames in archive, one per line, but do not extract. Use +\fB\-v\fP for more information (file size, etc). +.TP +.B \-L +Lowercase filesnames. Example: \fBFOO.TXT\fP will extract to \fBfoo.txt\fP\&. +.TP +.B \-o +Overwrite files, if they already exist. The default is to rename +existing files, adding a \fB~\fP suffix. Note that renaming \fBfile\fP +to \fBfile~\fP \fIwill\fP overwrite \fBfile~\fP if it already exists. +.TP +.B \-p +Extract to standard output. All the files that get extracted will be +printed to stdout with no delimiters, so this is most useful when +only extracting one file. \fB\-q\fP is automatically enabled by this option. +.TP +.B \-q +Quiet extraction: do not print "Uncrunching \fIfile\fP" messages. Errors +and warnings will still be printed. This is the default when \fB\-p\fP +is used. +.TP +.B \-v +Verbose listing of archive contents, with compressed and original +sizes, compression ration, date/time stamps, and checksum. The +output format is similar to that of \fBarc \-v\fP, minus the \fIStowage\fP +column, since \fIALF\fP doesn\(aqt support multiple compression types. The +date and time are displayed, but in most .alf files these are the +default values of "8 Jan 82 12:24a". +.TP +.BI \-x \ wildcard +Exclude (do not list or extract) files matching \fIwildcard\fP\&. See +\fBWILDCARDS\fP below. This option can be given multiple times +to exclude multiple patterns. .UNINDENT +.SH WILDCARDS +.sp +Wildcard (aka glob) matching works like it does on the Atari: \fI*.*\fP +matches any filename (whether or not it has an extension), and \fI*\fP +or \fI*.\fP only match filenames with no extension. +.sp +A \fI*\fP matches any number of characters (including zero). A \fI?\fP +matches a single character. +.sp +Like the Atari, anything in the name or extension following a \fI*\fP is +ignored: \fBFOO*BAR.TXT\fP is equivalent to \fBFOO*.TXT\fP, and +\fBFOO.T*T\fP is equivalent to \fBFOO.T*\fP\&. \fBFOO*TXT\fP is +equivalent to \fBFOO*\fP, and \fIwill not\fP match the file \fBFOO.TXT\fP\&. +.sp +Unlike the Atari, matching is case\-insensitive. .SH EXIT STATUS .sp \fB0\fP for success, \fB1\fP for failure. @@ -106,12 +165,10 @@ aka \fBDZ.COM\fP, with the following differences: .INDENT 0.0 .IP \(bu 2 There is no interactive mode. The file to extract must be given as -a command\-line argument. -.IP \(bu 2 -Files are always extracted to the current directory. The original -\fBUNALF.COM\fP accepted a 2nd argument or prompted for the output directory. +a command\-line argument, and the output directory must be given +with the \fB\-d\fP option. .IP \(bu 2 -This \fBunalf\fP is capable of listing the contents of an archive +This \fBunalf\fP is capable of listing or testing the contents of an archive without extracting it. .IP \(bu 2 Turning the screen off for speed makes no sense on modern operating diff --git a/src/unalf.c b/src/unalf.c index ba6405e..9a722f8 100644 --- a/src/unalf.c +++ b/src/unalf.c @@ -2,11 +2,18 @@ #include <unistd.h> #include <stdlib.h> #include <string.h> +#include <errno.h> +#include <sys/stat.h> #include <f65.h> #include "unalf.h" FILE *in_file, *out_file; char *in_filename, *self; +opts_t opts; +const char *exclude_globs[MAX_EXCLUDES]; +int exclude_count; +char * const *include_globs; +static void create_outdir(void); static void usage(void) { printf("usage: %s [-l] file.alf\n", self); @@ -21,35 +28,32 @@ static void set_self(char *argv0) { if(p) self = p + 1; } -int main(int argc, char **argv) { - char opt = 0; - int listonly = 0; +/* like "mkdir -p" (no error if dir already exists), + followed by "cd" (which will error if the existing + "directory" turns out to be a file or broken symlink */ +static void create_outdir(void) { + int r; - set_self(argv[0]); + r = mkdir(opts.outdir, 0777); + if(r < 0 && errno != EEXIST) { + fprintf(stderr, "%s: ", self); + perror(opts.outdir); + exit(1); + } - if(argc == 1) { - fprintf(stderr, "%s: missing required argument(s)\n", self); - usage(); - } else if(argc > 3) { - fprintf(stderr, "%s: too many arguments\n", self); - usage(); - } else if(argc == 2) { - in_filename = argv[1]; - } else { /* argc == 3 */ - in_filename = argv[2]; - if(argv[1][0] == '-') - opt = argv[1][1]; - else - opt = argv[1][0]; - if(opt == 'l' || opt == 'v') { - listonly = 1; - } else if(opt == 'x' || opt == 'e') { - /* NOP */ - } else { - fprintf(stderr, "%s: unknown option '-%c'\n", self, opt); - usage(); - } + if(chdir(opts.outdir) < 0) { + fprintf(stderr, "%s: ", self); + perror(opts.outdir); + exit(1); } +} + +int main(int argc, char **argv) { + set_self(argv[0]); + + if(argc < 2) usage(); + + parse_opts(argc, argv); if(!(in_file = fopen(in_filename, "rb"))) { fprintf(stderr, "%s: ", self); @@ -57,7 +61,9 @@ int main(int argc, char **argv) { exit(1); } - if(listonly) + if(opts.outdir) create_outdir(); + + if(opts.listonly) list_alf(); else extract_alf(); diff --git a/src/unalf.h b/src/unalf.h index 855ca80..030f6f2 100644 --- a/src/unalf.h +++ b/src/unalf.h @@ -1,3 +1,8 @@ +#ifndef u8 +#define u8 unsigned char +#define u16 unsigned short +#endif + /* converted from asm */ void uncrunch_file(void); void setup_io_bufs(void); @@ -16,6 +21,10 @@ void L79E7(void); void writeblock(void); void L7A5D(void); +/* glob.c */ +int globmatch(const char *pat, const char *arg); +int file_wanted(const char *filename); + /* io.c - asm rewritten in C */ int read_alf_header(void); void readblock(void); @@ -27,14 +36,40 @@ void open_output(void); /* listalf.c */ void list_alf(void); +unsigned int getquad(int offs); /* extract.c */ void extract_alf(void); void dpoke(int addr, u16 value); u16 dpeek(int addr); +void fix_filename(void); + +/* opts.c */ +void parse_opts(int argc, char * const *argv); /* unalf.c */ extern char *self; extern FILE *out_file; extern FILE *in_file; extern char *in_filename; + +typedef struct { + int txtconv; + int testonly; + int keepdot; + int listonly; + int lowercase; + const char *outdir; + int overwrite; + int extract_to_stdout; + int quiet; + char *exclude_glob; + int verbose_list; +} opts_t; + +extern opts_t opts; +extern const char *exclude_globs[256]; +extern char * const *include_globs; +extern int exclude_count; + +#define MAX_EXCLUDES 256 diff --git a/src/unalf.rst b/src/unalf.rst index 774ddf2..9eaa0ad 100644 --- a/src/unalf.rst +++ b/src/unalf.rst @@ -20,7 +20,7 @@ extract Atari 8-bit ALF archives SYNOPSIS ======== -unalf [**-l**] **alf-file** +unalf [**-aetklLopqv**] [**-d** *dir*] [**-x** *wildcard*] *alf-file* [*wildcard* ...] DESCRIPTION =========== @@ -31,29 +31,93 @@ DESCRIPTION not compatible with it. It was used on the Atari 8-bit platform beginning in the late 1980s. -Extracted files are written to the current directory. Existing files -are overwritten without warning. +Extracted files are written to the current directory by +default. Existing files are backed up by default (by adding a **~** +suffix to the filename). + +If one or more **wildcard** arguments are provided, files will only be +listed or extracted if they match one of the wildcards. See **WILDCARDS** +below for details. OPTIONS ======= -For compatibility with **arc**\(1) usage, the **-** in front of -option letters is optional. +-a + Convert text file line endings from Atari EOL to UNIX newline. Text + files are detected by name: **\*.txt**, **\*.doc**, **\*.lst** files + are considered text. --l - List contents of archive, but do not extract. The output format - is similar to that of **arc -v**, minus the *Stowage* column, since - *ALF* doesn't support multiple compression types. The date and time - are displayed, but in most .alf files these are the default values - of "8 Jan 82 12:24a". +-aa + Convert line endings in all extracted files. This will corrupt any + executables or non-text data files, so use with caution. --v - Same as **-l**. +-d output-dir + Write extracted files to this directory, which will be created if it + does not exist. The default is the current directory. -**-e**, **-x** +-e Extract files. This is actually a no-op, as extraction is the default action. +-k + Keep trailing periods. In most (all?) ALF files, filenames that have no + extension are stored with a trailing period, e.g. '**FOO**' would + be stored as '**FOO.**'. By default, **unalf** removes the trailing + period; use **-k** to keep it. + +-l + List filenames in archive, one per line, but do not extract. Use + **-v** for more information (file size, etc). + +-L + Lowercase filesnames. Example: **FOO.TXT** will extract to **foo.txt**. + +-o + Overwrite files, if they already exist. The default is to rename + existing files, adding a **~** suffix. Note that renaming **file** + to **file~** *will* overwrite **file~** if it already exists. + +-p + Extract to standard output. All the files that get extracted will be + printed to stdout with no delimiters, so this is most useful when + only extracting one file. **-q** is automatically enabled by this option. + +-q + Quiet extraction: do not print "Uncrunching *file*" messages. Errors + and warnings will still be printed. This is the default when **-p** + is used. + +-v + Verbose listing of archive contents, with compressed and original + sizes, compression ration, date/time stamps, and checksum. The + output format is similar to that of **arc -v**, minus the *Stowage* + column, since *ALF* doesn't support multiple compression types. The + date and time are displayed, but in most .alf files these are the + default values of "8 Jan 82 12:24a". + + +-x wildcard + Exclude (do not list or extract) files matching *wildcard*. See + **WILDCARDS** below. This option can be given multiple times + to exclude multiple patterns. + +WILDCARDS +========= + +Wildcard (aka glob) matching works like it does on the Atari: *\*.\** +matches any filename (whether or not it has an extension), and *\** +or *\*.* only match filenames with no extension. + +A *\** matches any number of characters (including zero). A *?* +matches a single character. + +Like the Atari, anything in the name or extension following a *\** is +ignored: **FOO\*BAR.TXT** is equivalent to **FOO\*.TXT**, and +**FOO.T\*T** is equivalent to **FOO.T\***. **FOO\*TXT** is +equivalent to **FOO\***, and *will not* match the file **FOO.TXT**. + +Unlike the Atari, matching is case-insensitive. + EXIT STATUS =========== @@ -93,12 +157,10 @@ This **unalf** is 100% compatible with the original Atari **UNALF.COM** aka **DZ.COM**, with the following differences: - There is no interactive mode. The file to extract must be given as - a command-line argument. - -- Files are always extracted to the current directory. The original - **UNALF.COM** accepted a 2nd argument or prompted for the output directory. + a command-line argument, and the output directory must be given + with the **-d** option. -- This **unalf** is capable of listing the contents of an archive +- This **unalf** is capable of listing or testing the contents of an archive without extracting it. - Turning the screen off for speed makes no sense on modern operating |
