Discussion:
Local Versus Global Command Options
Add Reply
Lawrence D'Oliveiro
2025-02-13 22:52:44 UTC
Reply
Permalink
One thing about DCL syntax is its ability to specify options (what it
calls “qualifiers”) either globally for the command, or locally to a
particular command argument:

«cmd»/«global-options» «arg1» «arg2» ...

where each «arg» can take the form

«subarg1»/«local-options-1»,«subarg2»/«local-options-2», ...

and then I think you can have plus signs as well as commas, to denote
another level of concatenation. And then you can put spaces
before/after commas and plus signs, and those are ignored and not
considered separators between arguments. So the presence or absence of
a comma or plus sign can become quite significant.

The traditional *nix command syntax never had provision for this. Most
commands take the fairly simple form

«cmd» «options» «args»

where each of «options» and «args», if nonempty consists of one or
more space-separated items, and all «options» are assumed to have
global effect, though some can add quite a lot of elaboration on this
where necessary.

(Did somebody say “dd”? Passing arguments by keyword, anybody?)

What’s the most complex *nix command you’ve come across? I think it
has to be ffmpeg. This looks broadly like

ffmpeg «local-input-options-1» -i «infile-1» \
«local-input-options-2» -i «infile-2» ... \
«local-output-options-1» «outfile-1» \
«local-output-options-2» «outfile-2» ...

The convention is that options apply to the immediately following file
specification: this is prefixed with “-i” for an input file, and is a
plain argument for an output file. Note the lack of “global” options:
all settings apply to a particular file.

But that’s not where it ends. Certain of the options can specify
“filtergraphs”, which are entire chains of effects operations to be
applied to a particular video or audio stream. The man page talks
about “simple” versus “complex” filtergraphs, but even the “simple”
ones can be pretty complex.

Filtergraphs can also be used during real-time playback, with the
“ffplay” command. For example:

ffplay -autoexit -vf scale=1152:864,setsar=0.9 \
'Sun Is Shining (Official Video).mp4'

That “-vf” option specifies a sequence of video filters, first to
scale up the video to make more use of my screen, and “setsar” (“Set
Source Aspect Ratio”) to fix distortion in the shape of the image
(everybody looking squashed) from the original digitization of the
video.

What would DCL-style syntax for ffmpeg look like? I suppose one
obvious equivalence would be

ffmpeg -
«infile-1»/«local-input-options-1»,«infile-2»/«local-input-options-2»,... -
«outfile-1»/«local-output-options-1»,«outfile-2»/«local-output-options-2»,... -

(being very careful about where the commas go), but what about the
syntax for filtergraphs? Would it be something like

/vf=(«filter-name-1»=«filter-params-1»,«filter-name-2»=«filter-params-2»...)

Does DCL have provision for this sort of complexity?
Arne Vajhøj
2025-02-14 01:06:47 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
What would DCL-style syntax for ffmpeg look like? I suppose one
obvious equivalence would be
ffmpeg -
«infile-1»/«local-input-options-1»,«infile-2»/«local-input-options-2»,... -
«outfile-1»/«local-output-options-1»,«outfile-2»/«local-output-options-2»,... -
(being very careful about where the commas go), but what about the
syntax for filtergraphs? Would it be something like
/vf=(«filter-name-1»=«filter-params-1»,«filter-name-2»=«filter-params-2»...)
Does DCL have provision for this sort of complexity?
You can do quite a bit with CDU, CLD and CLI$:

$ type fun.cld
define verb fun
image "sys$disk:[]fun"
parameter p1, value(type=$file)
parameter p2, value(type=$file)
parameter p3, value(type=$file)
parameter p4, value(type=$file)
parameter p5, value(type=$file)
parameter p6, value(type=$file)
parameter p7, value(type=$file)
parameter p8, value(type=$file)
qualifier q1, placement=local
qualifier q2, value(type=a_and_b, list, required), placement=local
define type a_and_b
keyword a, value(type=$number, required)
keyword b, value(type=$number, required)
$ type fun.pas
[inherit('sys$library:pascal$cli_routines')]
program fun(input,output);

type
pstr = varying [255] of char;

procedure check(pn : pstr);

var
fnm, a, b : pstr;

begin
if odd(cli$present(pn)) then begin
cli$get_value(pn, fnm.body, fnm.length);
write(pn, '=', fnm);
if odd(cli$present('Q1')) then begin
write(' Q1=present')
end else begin
write(' Q1=notpresent')
end;
if odd(cli$present('Q2')) then begin
cli$get_value('Q2.A', a.body, a.length);
write(' A=', a);
cli$get_value('Q2.B', b.body, b.length);
write(' B=', b);
end;
writeln;
end;
end;

begin
check('P1');
check('P2');
check('P3');
check('P4');
check('P5');
check('P6');
check('P7');
check('P8');
end.
$ pas fun
$ lin fun
$ set comm fun
$ fun x.dat /q2=(a:1,b:2) y.dat /q1 /q2=(a:3,b:4) z.dat /q2=(a:5,b:6)
P1=x.dat Q1=notpresent A=1 B=2
P2=y.dat Q1=present A=3 B=4
P3=z.dat Q1=notpresent A=5 B=6

Arne
Arne Vajhøj
2025-02-14 02:37:04 UTC
Reply
Permalink
Post by Arne Vajhøj
Post by Lawrence D'Oliveiro
What would DCL-style syntax for ffmpeg look like? I suppose one
obvious equivalence would be
     ffmpeg -
         «infile-1»/«local-input-options-1»,«infile-2»/«local-input-
options-2»,... -
         «outfile-1»/«local-output-options-1»,«outfile-2»/«local-
output-options-2»,... -
(being very careful about where the commas go), but what about the
syntax for filtergraphs? Would it be something like
     /vf=(«filter-name-1»=«filter-params-1»,«filter-name-2»=«filter-
params-2»...)
Does DCL have provision for this sort of complexity?
$ type fun.cld
define verb fun
    image "sys$disk:[]fun"
    parameter p1, value(type=$file)
    parameter p2, value(type=$file)
    parameter p3, value(type=$file)
    parameter p4, value(type=$file)
    parameter p5, value(type=$file)
    parameter p6, value(type=$file)
    parameter p7, value(type=$file)
    parameter p8, value(type=$file)
qualifier q1, placement=local
qualifier q2, value(type=a_and_b, list, required), placement=local
define type a_and_b
    keyword a, value(type=$number, required)
    keyword b, value(type=$number, required)
$ type fun.pas
[inherit('sys$library:pascal$cli_routines')]
program fun(input,output);
type
   pstr = varying [255] of char;
procedure check(pn : pstr);
var
   fnm, a, b : pstr;
begin
   if odd(cli$present(pn)) then begin
      cli$get_value(pn, fnm.body, fnm.length);
      write(pn, '=', fnm);
      if odd(cli$present('Q1')) then begin
         write(' Q1=present')
      end else begin
         write(' Q1=notpresent')
      end;
      if odd(cli$present('Q2')) then begin
         cli$get_value('Q2.A', a.body, a.length);
         write(' A=', a);
         cli$get_value('Q2.B', b.body, b.length);
         write(' B=', b);
      end;
      writeln;
   end;
end;
begin
   check('P1');
   check('P2');
   check('P3');
   check('P4');
   check('P5');
   check('P6');
   check('P7');
   check('P8');
end.
$ pas fun
$ lin fun
$ set comm fun
$ fun x.dat /q2=(a:1,b:2) y.dat /q1 /q2=(a:3,b:4) z.dat /q2=(a:5,b:6)
P1=x.dat Q1=notpresent A=1 B=2
P2=y.dat Q1=present A=3 B=4
P3=z.dat Q1=notpresent A=5 B=6
Or with a P1 list:

$ type fun2.cld
define verb fun2
image "sys$disk:[]fun2"
parameter p1, value(type=$file, list, required)
qualifier q1, placement=local
qualifier q2, value(type=a_and_b, list, required), placement=local
define type a_and_b
keyword a, value(type=$number, required)
keyword b, value(type=$number, required)
$ type fun2.pas
[inherit('sys$library:pascal$cli_routines')]
program fun2(input,output);

type
pstr = varying [255] of char;

var
fnm, a, b : pstr;

begin
while odd(cli$get_value('P1', fnm.body, fnm.length)) do begin
write(fnm);
if odd(cli$present('Q1')) then begin
write(' Q1=present')
end else begin
write(' Q1=notpresent')
end;
if odd(cli$present('Q2')) then begin
cli$get_value('Q2.A', a.body, a.length);
write(' A=', a);
cli$get_value('Q2.B', b.body, b.length);
write(' B=', b);
end;
writeln;
end;
end.
$ pas fun2
$ lin fun2
$ set comm fun2
$ fun2 x.dat/q2=(a:1,b:2), y.dat/q1/q2=(a:3,b:4), z.dat/q2=(a:5,b:6)
x.dat Q1=notpresent A=1 B=2
y.dat Q1=present A=3 B=4
z.dat Q1=notpresent A=5 B=6

Arne
Simon Clubley
2025-02-14 13:38:59 UTC
Reply
Permalink
What?s the most complex *nix command you?ve come across? I think it
has to be ffmpeg. This looks broadly like
ffmpeg «local-input-options-1» -i «infile-1» \
«local-input-options-2» -i «infile-2» ... \
«local-output-options-1» «outfile-1» \
«local-output-options-2» «outfile-2» ...
The convention is that options apply to the immediately following file
specification: this is prefixed with ?-i? for an input file, and is a
all settings apply to a particular file.
But that?s not where it ends. Certain of the options can specify
?filtergraphs?, which are entire chains of effects operations to be
applied to a particular video or audio stream. The man page talks
about ?simple? versus ?complex? filtergraphs, but even the ?simple?
ones can be pretty complex.
Filtergraphs can also be used during real-time playback, with the
ffplay -autoexit -vf scale=1152:864,setsar=0.9 \
'Sun Is Shining (Official Video).mp4'
That ?-vf? option specifies a sequence of video filters, first to
scale up the video to make more use of my screen, and ?setsar? (?Set
Source Aspect Ratio?) to fix distortion in the shape of the image
(everybody looking squashed) from the original digitization of the
video.
What would DCL-style syntax for ffmpeg look like? I suppose one
obvious equivalence would be
ffmpeg -
«infile-1»/«local-input-options-1»,«infile-2»/«local-input-options-2»,... -
«outfile-1»/«local-output-options-1»,«outfile-2»/«local-output-options-2»,... -
(being very careful about where the commas go), but what about the
syntax for filtergraphs? Would it be something like
/vf=(«filter-name-1»=«filter-params-1»,«filter-name-2»=«filter-params-2»...)
Does DCL have provision for this sort of complexity?
Not in any meaningful way. There's no way to validate the syntax or
parameters of a filter or other ffmpeg syntax with DCL. For a simple
example, when merging two files, one with an audio stream and one with
a video stream, into a MP4 output container then specifying the input and
output video stream numbers would be a parameter along the lines of "0:v:0".

How would you even specify in DCL the first and last fields are numbers
and the middle field is a letter from a list of valid values ?

You can use DCL syntax in the way you specify, but the vast majority
of the parsing would still have to be done in ffmpeg as it is at the
moment. DCL syntax doesn't really give you anything extra.

Similar comments apply to mplayer BTW, and to make something clear to
people reading this (which is only implied above), the filters MUST be
executed in the order given. For example, with mplayer, I might first
use a crop filter to get rid of black bars within a frame and then
apply a scale filter to the resulting frame.

Simon.
--
Simon Clubley, ***@remove_me.eisner.decus.org-Earth.UFP
Walking destinations on a map are further away than they appear.
Arne Vajhøj
2025-02-14 16:33:32 UTC
Reply
Permalink
Post by Simon Clubley
Post by Lawrence D'Oliveiro
What would DCL-style syntax for ffmpeg look like? I suppose one
obvious equivalence would be
ffmpeg -
«infile-1»/«local-input-options-1»,«infile-2»/«local-input-options-2»,... -
«outfile-1»/«local-output-options-1»,«outfile-2»/«local-output-options-2»,... -
(being very careful about where the commas go), but what about the
syntax for filtergraphs? Would it be something like
/vf=(«filter-name-1»=«filter-params-1»,«filter-name-2»=«filter-params-2»...)
Does DCL have provision for this sort of complexity?
Not in any meaningful way. There's no way to validate the syntax or
parameters of a filter or other ffmpeg syntax with DCL. For a simple
example, when merging two files, one with an audio stream and one with
a video stream, into a MP4 output container then specifying the input and
output video stream numbers would be a parameter along the lines of "0:v:0".
How would you even specify in DCL the first and last fields are numbers
and the middle field is a letter from a list of valid values ?
You can use DCL syntax in the way you specify, but the vast majority
of the parsing would still have to be done in ffmpeg as it is at the
moment. DCL syntax doesn't really give you anything extra.
You can define a bit in CLD. Including specifying that
something is a number and something else is from an
enumeration list.

$ type fun3.cld
define verb fun3
image "sys$disk:[]fun3"
parameter p1, value(type=$file, list, required)
qualifier q, value(type=threeitems, list, required), placement=local
define type threeitems
keyword a, value(type=$number, required)
keyword b, value(type=$quoted_string, required)
keyword c, value(type=myenum, required)
define type myenum
keyword x
keyword y
keyword z
$ type fun3.pas
[inherit('sys$library:pascal$cli_routines')]
program fun3(input,output);

type
pstr = varying [255] of char;

var
fnm, a, b, c : pstr;

begin
while odd(cli$get_value('P1', fnm.body, fnm.length)) do begin
write(fnm);
if odd(cli$present('Q')) then begin
cli$get_value('Q.A', a.body, a.length);
write(' A=', a);
cli$get_value('Q.B', b.body, b.length);
write(' B=', b);
cli$get_value('Q.C', c.body, c.length);
write(' C=', c);
end;
writeln;
end;
end.
$ pas fun3
$ lin fun3
$ set comm fun3
$ fun3 x.dat/q=(a:1,b:"This is x",c:x), y.dat/q=(a:2,b:"This is y",c:y),
z.dat/q=(a:3,b:"This is z",c:z)
x.dat A=1 B="This is x" C=X
y.dat A=2 B="This is y" C=Y
z.dat A=3 B="This is z" C=Z
$ on error then continue
$ fun3 x.dat/q=(a:x,b:"This is x",c:x), y.dat/q=(a:2,b:"This is y",c:y),
z.dat/q=(a:3,b:"This is z",c:z)
%DCL-W-NUMBER, invalid numeric value - supply an integer
\X\
$ fun3 x.dat/q=(a:1,b:"This is x",c:xdat), y.dat/q=(a:2,b:"This is
y",c:y), z.dat/q=(a:3,b:"This is z",c:z)
%DCL-W-IVKEYW, unrecognized keyword - check validity and spelling
\XDAT\

Arne
Simon Clubley
2025-02-14 19:02:47 UTC
Reply
Permalink
Post by Arne Vajhøj
You can define a bit in CLD. Including specifying that
something is a number and something else is from an
enumeration list.
$ type fun3.cld
define verb fun3
image "sys$disk:[]fun3"
parameter p1, value(type=$file, list, required)
qualifier q, value(type=threeitems, list, required), placement=local
define type threeitems
keyword a, value(type=$number, required)
keyword b, value(type=$quoted_string, required)
keyword c, value(type=myenum, required)
define type myenum
keyword x
keyword y
keyword z
$ type fun3.pas
[inherit('sys$library:pascal$cli_routines')]
program fun3(input,output);
type
pstr = varying [255] of char;
var
fnm, a, b, c : pstr;
begin
while odd(cli$get_value('P1', fnm.body, fnm.length)) do begin
write(fnm);
if odd(cli$present('Q')) then begin
cli$get_value('Q.A', a.body, a.length);
write(' A=', a);
cli$get_value('Q.B', b.body, b.length);
write(' B=', b);
cli$get_value('Q.C', c.body, c.length);
write(' C=', c);
end;
writeln;
end;
end.
$ pas fun3
$ lin fun3
$ set comm fun3
$ fun3 x.dat/q=(a:1,b:"This is x",c:x), y.dat/q=(a:2,b:"This is y",c:y),
z.dat/q=(a:3,b:"This is z",c:z)
x.dat A=1 B="This is x" C=X
y.dat A=2 B="This is y" C=Y
z.dat A=3 B="This is z" C=Z
$ on error then continue
$ fun3 x.dat/q=(a:x,b:"This is x",c:x), y.dat/q=(a:2,b:"This is y",c:y),
z.dat/q=(a:3,b:"This is z",c:z)
%DCL-W-NUMBER, invalid numeric value - supply an integer
\X\
$ fun3 x.dat/q=(a:1,b:"This is x",c:xdat), y.dat/q=(a:2,b:"This is
y",c:y), z.dat/q=(a:3,b:"This is z",c:z)
%DCL-W-IVKEYW, unrecognized keyword - check validity and spelling
\XDAT\
But it is no longer ffmpeg syntax, but convoluted DCL syntax. It also
doesn't help you with the main problem I mentioned, which is parsing
and validating the filter syntax. To give you an idea of the scale of
the problem, here is the mplayer man page (which I know a lot better
than the ffmpeg filters):

https://linux.die.net/man/1/mplayer

That is a _very_ long man page, so search for "-vf-add" and you should
be in a section marked "Video Filters". In that section are the list of
available filters, starting with the crop one I mentioned earlier.

How would you turn the list of filters, each with their own syntax, into
something that can be validated by DCL ? As a reminder, it is critical
that the filters are available to the program in the order they were
specified on the command line.

Simon.
--
Simon Clubley, ***@remove_me.eisner.decus.org-Earth.UFP
Walking destinations on a map are further away than they appear.
Arne Vajhøj
2025-02-14 23:49:13 UTC
Reply
Permalink
Post by Simon Clubley
Post by Arne Vajhøj
You can define a bit in CLD. Including specifying that
something is a number and something else is from an
enumeration list.
...
Post by Simon Clubley
Post by Arne Vajhøj
$ fun3 x.dat/q=(a:1,b:"This is x",c:x), y.dat/q=(a:2,b:"This is y",c:y),
z.dat/q=(a:3,b:"This is z",c:z)
x.dat A=1 B="This is x" C=X
y.dat A=2 B="This is y" C=Y
z.dat A=3 B="This is z" C=Z
$ on error then continue
$ fun3 x.dat/q=(a:x,b:"This is x",c:x), y.dat/q=(a:2,b:"This is y",c:y),
z.dat/q=(a:3,b:"This is z",c:z)
%DCL-W-NUMBER, invalid numeric value - supply an integer
\X\
$ fun3 x.dat/q=(a:1,b:"This is x",c:xdat), y.dat/q=(a:2,b:"This is
y",c:y), z.dat/q=(a:3,b:"This is z",c:z)
%DCL-W-IVKEYW, unrecognized keyword - check validity and spelling
\XDAT\
But it is no longer ffmpeg syntax, but convoluted DCL syntax.
DCL use DCL syntax. That is how it is.
Post by Simon Clubley
It also
doesn't help you with the main problem I mentioned, which is parsing
and validating the filter syntax.
How would you turn the list of filters, each with their own syntax, into
something that can be validated by DCL ? As a reminder, it is critical
that the filters are available to the program in the order they were
specified on the command line.
I think that can be define in CLD.

$ type fun4.cld
define verb fun4
image "sys$disk:[]fun4"
parameter p1, value(type=$file, list, required)
qualifier filter, value(type=filter_type, list, required), placement=local
define type filter_type
keyword filtera, value(type=filtera_type, list, required)
keyword filterb, value(type=filterb_type, list, required)
define type filtera_type
keyword a1, value(type=$number, required)
keyword a2, value(type=$number, required)
keyword x, value(type=$number, required)
define type filterb_type
keyword b1, value(type=$number, required)
keyword b2, value(type=$number, required)
keyword x, value(type=$number, required)
$ type fun4.pas
[inherit('sys$library:pascal$cli_routines')]
program fun4(input,output);

type
pstr = varying [255] of char;
filter_type = (filtera, filterb);

var
filter_list : array [1..100] of filter_type;
fnm, filter, a1, a2, b1, b2, x : pstr;
nfilters, i : integer;

begin
while odd(cli$get_value('P1', fnm.body, fnm.length)) do begin
write(fnm);
nfilters := 0;
while odd(cli$get_value('FILTER', filter.body, filter.length)) do
begin
nfilters := nfilters + 1;
if index(filter, 'FILTERA') = 1 then
filter_list[nfilters] := filtera
else if index(filter, 'FILTERB') = 1 then
filter_list[nfilters] := filterb
else
halt;
end;
for i := 1 to nfilters do begin
write(' ', filter_list[i]);
case filter_list[i] of
filtera:
begin
write(' filtera');
cli$get_value('FILTERA.A1', a1.body, a1.length);
write(' a1=', a1);
cli$get_value('FILTERA.A2', a2.body, a2.length);
write(' a2=', a2);
cli$get_value('FILTERA.X', x.body, x.length);
write(' x=', x);
end;
filterb:
begin
write(' filterb');
cli$get_value('FILTERB.B1', b1.body, b1.length);
write(' b1=', b1);
cli$get_value('FILTERB.B2', b2.body, b2.length);
write(' b2=', b2);
cli$get_value('FILTERB.X', x.body, x.length);
write(' x=', x);
end;
end;
end;
writeln;
end;
end.
$ pas fun4
$ lin fun4
$ set comm fun4
$ fun4
f1.dat/filter=(filtera=(a1:12,a2:34,x:1234),filterb=(b1:56,b2:78,x:5678)),-

f2.dat/filter=(filterb=(b2:87,b1:65,x:8765),filtera=(a2:43,a1:21,x:4321))
f1.dat FILTERA filtera a1=12 a2=34 x=1234 FILTERB filterb b1=56 b2=78
x=5678
f2.dat FILTERB filterb b1=65 b2=87 x=8765 FILTERA filtera a1=21 a2=43
x=4321

Arne
Arne Vajhøj
2025-02-15 01:02:20 UTC
Reply
Permalink
Post by Arne Vajhøj
                                                             It also
doesn't help you with the main problem I mentioned, which is parsing
and validating the filter syntax.
How would you turn the list of filters, each with their own syntax, into
something that can be validated by DCL ? As a reminder, it is critical
that the filters are available to the program in the order they were
specified on the command line.
I think that can be define in CLD.
$ type fun4.cld
define verb fun4
    image "sys$disk:[]fun4"
    parameter p1, value(type=$file, list, required)
qualifier filter, value(type=filter_type, list, required), placement=local
define type filter_type
    keyword filtera, value(type=filtera_type, list, required)
    keyword filterb, value(type=filterb_type, list, required)
define type filtera_type
    keyword a1, value(type=$number, required)
    keyword a2, value(type=$number, required)
    keyword x, value(type=$number, required)
define type filterb_type
    keyword b1, value(type=$number, required)
    keyword b2, value(type=$number, required)
    keyword x, value(type=$number, required)
$ type fun4.pas
[inherit('sys$library:pascal$cli_routines')]
program fun4(input,output);
type
   pstr = varying [255] of char;
   filter_type = (filtera, filterb);
var
   filter_list : array [1..100] of filter_type;
   fnm, filter, a1, a2, b1, b2, x : pstr;
   nfilters, i : integer;
begin
   while odd(cli$get_value('P1', fnm.body, fnm.length)) do begin
      write(fnm);
      nfilters := 0;
      while odd(cli$get_value('FILTER', filter.body, filter.length)) do
begin
         nfilters := nfilters + 1;
         if index(filter, 'FILTERA') = 1 then
            filter_list[nfilters] := filtera
         else if index(filter, 'FILTERB') = 1 then
            filter_list[nfilters] := filterb
         else
            halt;
      end;
      for i := 1 to nfilters do begin
         write(' ', filter_list[i]);
         case filter_list[i] of
               begin
                  write(' filtera');
                  cli$get_value('FILTERA.A1', a1.body, a1.length);
                  write(' a1=', a1);
                  cli$get_value('FILTERA.A2', a2.body, a2.length);
                  write(' a2=', a2);
                  cli$get_value('FILTERA.X', x.body, x.length);
                  write(' x=', x);
               end;
               begin
                  write(' filterb');
                  cli$get_value('FILTERB.B1', b1.body, b1.length);
                  write(' b1=', b1);
                  cli$get_value('FILTERB.B2', b2.body, b2.length);
                  write(' b2=', b2);
                  cli$get_value('FILTERB.X', x.body, x.length);
                  write(' x=', x);
               end;
         end;
      end;
      writeln;
   end;
end.
$ pas fun4
$ lin fun4
$ set comm fun4
$ fun4 f1.dat/
filter=(filtera=(a1:12,a2:34,x:1234),filterb=(b1:56,b2:78,x:5678)),-
f2.dat/filter=(filterb=(b2:87,b1:65,x:8765),filtera=(a2:43,a1:21,x:4321))
f1.dat  FILTERA filtera a1=12 a2=34 x=1234  FILTERB filterb b1=56 b2=78 x=5678
f2.dat  FILTERB filterb b1=65 b2=87 x=8765  FILTERA filtera a1=21 a2=43 x=4321
But I like this version better:

$ type fun5.cld
define verb fun5
image "sys$disk:[]fun5"
parameter p1, value(type=$file, list, required)
qualifier filter, value(type=$quoted_string, list, required),
placement=local
$ type fun5sup.cld
module fun5sup
define verb fa
qualifier a1, value(type=$number, required)
qualifier a2, value(type=$number, required)
qualifier x, value(type=$number, required)
define verb fb
qualifier b1, value(type=$number, required)
qualifier b2, value(type=$number, required)
qualifier x, value(type=$number, required)
$ type fun5.pas
[inherit('sys$library:pascal$cli_routines')]
program fun5(input,output);

type
pstr = varying [255] of char;
finfo = record
fnm : pstr;
nfilter : integer;
filter_list : array [1..100] of pstr;
end;

var
fun5sup : [external] integer;

var
finfo_list : array [1..100] of finfo;
fnm, filter, a1, a2, b1, b2, x : pstr;
n, i, j : integer;

begin
n := 0;
while odd(cli$get_value('P1', fnm.body, fnm.length)) do begin
n := n + 1;
finfo_list[n].fnm := fnm;
finfo_list[n].nfilter := 0;
while odd(cli$get_value('FILTER', filter.body, filter.length)) do
begin
finfo_list[n].nfilter := finfo_list[n].nfilter + 1;
finfo_list[n].filter_list[finfo_list[n].nfilter] :=
substr(filter, 2, length(filter) - 2);
end;
end;
for i := 1 to n do begin
write(finfo_list[i].fnm);
for j := 1 to finfo_list[i].nfilter do begin
cli$dcl_parse(finfo_list[i].filter_list[j], fun5sup);
if index(finfo_list[i].filter_list[j], 'fa') = 1 then begin
write(' fa');
cli$get_value('A1', a1.body, a1.length);
write(' a1=', a1);
cli$get_value('A2', a2.body, a2.length);
write(' a2=', a2);
cli$get_value('X', x.body, x.length);
write(' x=', x);
end else if index(finfo_list[i].filter_list[j], 'fb') = 1 then
begin
write(' fb');
cli$get_value('B1', b1.body, b1.length);
write(' b1=', b1);
cli$get_value('B2', b2.body, b2.length);
write(' b2=', b2);
cli$get_value('X', x.body, x.length);
write(' x=', x);
end else begin
halt;
end;
end;
writeln;
end;
end.
$ set comm/obj fun5sup
$ pas fun5
$ lin fun5 + fun5sup
$ set comm fun5
$ fun5 f1.dat/filter=("fa /a1=12 /a2:34 /x=1234","fb /b1=56 /b2=78
/x=5678"),-
f2.dat/filter=("fb /b2=87 /b1:65 /x:8765","fa /a2=43 /a1=21
/x=4321")
f1.dat fa a1=12 a2=34 x=1234 fb b1=56 b2=78 x=5678
f2.dat fb b1=65 b2=87 x=8765 fa a1=21 a2=43 x=4321

Arne
Mark Berryman
2025-02-15 19:22:59 UTC
Reply
Permalink
Post by Arne Vajhøj
Post by Arne Vajhøj
                                                             It also
doesn't help you with the main problem I mentioned, which is parsing
and validating the filter syntax.
How would you turn the list of filters, each with their own syntax, into
something that can be validated by DCL ? As a reminder, it is critical
that the filters are available to the program in the order they were
specified on the command line.
I think that can be define in CLD.
$ type fun4.cld
define verb fun4
     image "sys$disk:[]fun4"
     parameter p1, value(type=$file, list, required)
qualifier filter, value(type=filter_type, list, required),
placement=local
define type filter_type
     keyword filtera, value(type=filtera_type, list, required)
     keyword filterb, value(type=filterb_type, list, required)
define type filtera_type
     keyword a1, value(type=$number, required)
     keyword a2, value(type=$number, required)
     keyword x, value(type=$number, required)
define type filterb_type
     keyword b1, value(type=$number, required)
     keyword b2, value(type=$number, required)
     keyword x, value(type=$number, required)
$ type fun4.pas
[inherit('sys$library:pascal$cli_routines')]
program fun4(input,output);
type
    pstr = varying [255] of char;
    filter_type = (filtera, filterb);
var
    filter_list : array [1..100] of filter_type;
    fnm, filter, a1, a2, b1, b2, x : pstr;
    nfilters, i : integer;
begin
    while odd(cli$get_value('P1', fnm.body, fnm.length)) do begin
       write(fnm);
       nfilters := 0;
       while odd(cli$get_value('FILTER', filter.body, filter.length))
do begin
          nfilters := nfilters + 1;
          if index(filter, 'FILTERA') = 1 then
             filter_list[nfilters] := filtera
          else if index(filter, 'FILTERB') = 1 then
             filter_list[nfilters] := filterb
          else
             halt;
       end;
       for i := 1 to nfilters do begin
          write(' ', filter_list[i]);
          case filter_list[i] of
                begin
                   write(' filtera');
                   cli$get_value('FILTERA.A1', a1.body, a1.length);
                   write(' a1=', a1);
                   cli$get_value('FILTERA.A2', a2.body, a2.length);
                   write(' a2=', a2);
                   cli$get_value('FILTERA.X', x.body, x.length);
                   write(' x=', x);
                end;
                begin
                   write(' filterb');
                   cli$get_value('FILTERB.B1', b1.body, b1.length);
                   write(' b1=', b1);
                   cli$get_value('FILTERB.B2', b2.body, b2.length);
                   write(' b2=', b2);
                   cli$get_value('FILTERB.X', x.body, x.length);
                   write(' x=', x);
                end;
          end;
       end;
       writeln;
    end;
end.
$ pas fun4
$ lin fun4
$ set comm fun4
$ fun4 f1.dat/
filter=(filtera=(a1:12,a2:34,x:1234),filterb=(b1:56,b2:78,x:5678)),-
f2.dat/filter=(filterb=(b2:87,b1:65,x:8765),filtera=(a2:43,a1:21,x:4321))
f1.dat  FILTERA filtera a1=12 a2=34 x=1234  FILTERB filterb b1=56 b2=78 x=5678
f2.dat  FILTERB filterb b1=65 b2=87 x=8765  FILTERA filtera a1=21 a2=43 x=4321
$ type fun5.cld
define verb fun5
    image "sys$disk:[]fun5"
    parameter p1, value(type=$file, list, required)
qualifier filter, value(type=$quoted_string, list, required),
placement=local
$ type fun5sup.cld
module fun5sup
define verb fa
    qualifier a1, value(type=$number, required)
    qualifier a2, value(type=$number, required)
    qualifier x, value(type=$number, required)
define verb fb
    qualifier b1, value(type=$number, required)
    qualifier b2, value(type=$number, required)
    qualifier x, value(type=$number, required)
$ type fun5.pas
[inherit('sys$library:pascal$cli_routines')]
program fun5(input,output);
type
   pstr = varying [255] of char;
   finfo = record
              fnm : pstr;
              nfilter : integer;
              filter_list : array [1..100] of pstr;
           end;
var
   fun5sup : [external] integer;
var
   finfo_list : array [1..100] of finfo;
   fnm, filter, a1, a2, b1, b2, x : pstr;
   n, i, j : integer;
begin
   n := 0;
   while odd(cli$get_value('P1', fnm.body, fnm.length)) do begin
      n := n + 1;
      finfo_list[n].fnm := fnm;
      finfo_list[n].nfilter := 0;
      while odd(cli$get_value('FILTER', filter.body, filter.length)) do
begin
         finfo_list[n].nfilter := finfo_list[n].nfilter + 1;
         finfo_list[n].filter_list[finfo_list[n].nfilter] :=
substr(filter, 2, length(filter) - 2);
      end;
   end;
   for i := 1 to n do begin
      write(finfo_list[i].fnm);
      for j := 1 to finfo_list[i].nfilter do begin
         cli$dcl_parse(finfo_list[i].filter_list[j], fun5sup);
         if index(finfo_list[i].filter_list[j], 'fa') = 1 then begin
            write(' fa');
            cli$get_value('A1', a1.body, a1.length);
            write(' a1=', a1);
            cli$get_value('A2', a2.body, a2.length);
            write(' a2=', a2);
            cli$get_value('X', x.body, x.length);
            write(' x=', x);
         end else if index(finfo_list[i].filter_list[j], 'fb') = 1 then
begin
            write(' fb');
            cli$get_value('B1', b1.body, b1.length);
            write(' b1=', b1);
            cli$get_value('B2', b2.body, b2.length);
            write(' b2=', b2);
            cli$get_value('X', x.body, x.length);
            write(' x=', x);
         end else begin
            halt;
         end;
      end;
      writeln;
   end;
end.
$ set comm/obj fun5sup
$ pas fun5
$ lin fun5 + fun5sup
$ set comm fun5
$ fun5 f1.dat/filter=("fa /a1=12 /a2:34 /x=1234","fb /b1=56 /b2=78 /
x=5678"),-
       f2.dat/filter=("fb /b2=87 /b1:65 /x:8765","fa /a2=43 /a1=21 /
x=4321")
f1.dat fa a1=12 a2=34 x=1234 fb b1=56 b2=78 x=5678
f2.dat fb b1=65 b2=87 x=8765 fa a1=21 a2=43 x=4321
Way back when an Alpha workstation was my desktop, it had a good
graphics card and a sound card. During this time I got mplayer running
on VMS and was even able to use it to play DVDs. Sadly, subsequent to
that time, itanium was too noisy for a desktop and VMS on x86 has no
multimedia support.

On *nix, one's program can define any command syntax desired since the
command-line parsing is done entirely within the program. The shell
doesn't really do anything except try to expand unquoted wildcards
(which somewhat limits the use of wildcards since the program cannot
differentiate between a source wildcard and a destination wildcard). On
VMS, DCL can do it either way. One can define a syntax that allows DCL
to do the parsing as Arne has shown, or one can tell DCL to simply pass
the command line to the program and let the program do all of the
parsing. One advantage of the former is that if the command-line fails
to parse, the program never even has to be activated.

So, IMHO, DCL is superior in this regard. The one thing I wish it had,
in regards to command-line parsing, was a setting that meant "set the
PIPE command as default". I am one of those users who only has ODS-5
disks, always has parse_style set to extended, and all my utilities
still using *nix syntax use lib$initialize.

Mark Berryman
Arne Vajhøj
2025-02-15 20:20:40 UTC
Reply
Permalink
Post by Mark Berryman
On *nix, one's program can define any command syntax desired since the
command-line parsing is done entirely within the program.  The shell
doesn't really do anything except try to expand unquoted wildcards
(which somewhat limits the use of wildcards since the program cannot
differentiate between a source wildcard and a destination wildcard).  On
VMS, DCL can do it either way.  One can define a syntax that allows DCL
to do the parsing as Arne has shown, or one can tell DCL to simply pass
the command line to the program and let the program do all of the
parsing.  One advantage of the former is that if the command-line fails
to parse, the program never even has to be activated.
A VMS program has choices:
1A) SET COMM and CLI$
1B) SET COMM/OBJ, LIB$GET_FOREIGN and CLI$
2) LIB$GET_FOREIGN and custom parsing
3) Language specific way to get individual arguments

Arne
Arne Vajhøj
2025-02-18 02:41:07 UTC
Reply
Permalink
Post by Arne Vajhøj
Post by Mark Berryman
On *nix, one's program can define any command syntax desired since the
command-line parsing is done entirely within the program.  The shell
doesn't really do anything except try to expand unquoted wildcards
(which somewhat limits the use of wildcards since the program cannot
differentiate between a source wildcard and a destination wildcard).
On VMS, DCL can do it either way.  One can define a syntax that allows
DCL to do the parsing as Arne has shown, or one can tell DCL to simply
pass the command line to the program and let the program do all of the
parsing.  One advantage of the former is that if the command-line
fails to parse, the program never even has to be activated.
1A) SET COMM and CLI$
1B) SET COMM/OBJ, LIB$GET_FOREIGN and CLI$
2) LIB$GET_FOREIGN and custom parsing
3) Language specific way to get individual arguments
Pascal demo:

$ type cl1a.cld
define verb cl1a
image "sys$disk:[]cl1a"
qualifier f, value(type=$file, required)
$ set comm cl1a
$ type cl1a.pas
[inherit('pascal$cli_routines')]
program cl1a(input,output);

type
pstr = varying [255] of char;

var
f : pstr;

begin
cli$get_value('F', f.body, f.length);
writeln('Pascal CL1A F=', f);
end.
$ pas cl1a
$ link cl1a
$ cl1a /f=z.z
Pascal CL1A F=z.z
$ type cl1bsup.cld
module cl1bsup
define verb cl1b
qualifier f, value(type=$file, required)
$ type cl1b.pas
[inherit('sys$library:pascal$lib_routines',
'sys$library:pascal$cli_routines')]
program cl1b(input,output);

type
pstr = varying [255] of char;

var
cl1bsup : [external] integer;
cmdlin : pstr;
f : pstr;
i : integer;

begin
lib$get_foreign(cmdlin.body, , cmdlin.length);
for i := 1 to length(cmdlin) do begin
if cmdlin[i] = '-' then begin
cmdlin[i] := '/';
end else if cmdlin[i] = ' ' then begin
cmdlin[i] := '=';
end;
end;
cli$dcl_parse('cl1b ' + cmdlin, cl1bsup);
cli$get_value('F', f.body, f.length);
writeln('Pascal CL1B F=', f);
end.
$ set comm/obj cl1bsup
$ pas cl1b
$ link cl1b + cl1bsup
$ cl1b :== $sys$disk:[]cl1b
$ cl1b /f=z.z
Pascal CL1B F=z.z
$ cl1b -f z.z
Pascal CL1B F=z.z
$ type cl2.pas
[inherit('sys$library:pascal$lib_routines')]
program cl1b(input,output);

type
pstr = varying [255] of char;

var
cmdlin : pstr;
f : pstr;
ix : integer;

begin
lib$get_foreign(cmdlin.body, , cmdlin.length);
ix := index(cmdlin, '-f ');
f := substr(cmdlin, ix + 3, length(cmdlin) - ix - 2);
writeln('Pascal CL2 F=', f);
end.
$ pas cl2
$ link cl2
$ cl2 :== $sys$disk:[]cl2
$ cl2 -f z.z
Pascal CL2 F=z.z

C demo:

$ type cl1a.cld
define verb cl1a
image "sys$disk:[]cl1a"
qualifier f, value(type=$file, required)
$ set comm cl1a
$ type cl1a.c
#include <stdio.h>
#include <stdint.h>

#include <descrip.h>
#include <cli$routines.h>

int main()
{
char f[255];
int16_t flen;
$DESCRIPTOR(fnamedesc, "F");
$DESCRIPTOR(fdesc, f);
cli$get_value(&fnamedesc, &fdesc, &flen);
f[flen] = 0;
printf("C CL1A F=%s\n", f);
return 0;
}

$ cc cl1a
$ link cl1a
$ cl1a /f=z.z
C CL1A F=z.z
$ type cl1bsup.cld
module cl1bsup
define verb cl1b
qualifier f, value(type=$file, required)
$ type cl1b.c
#include <stdio.h>
#include <string.h>
#include <stdint.h>

#include <descrip.h>
#include <lib$routines.h>
#include <cli$routines.h>

globalvalue int32_t cl1bsup;

int main()
{
char cmdlin[256];
int16_t cmdlinlen;
$DESCRIPTOR(cmdlindesc, cmdlin);
lib$get_foreign(&cmdlindesc, 0, &cmdlinlen);
cmdlin[cmdlinlen] = 0;
for(int i = 0; i < cmdlinlen; i++)
{
if(cmdlin[i] == '-') cmdlin[i] = '/';
if(cmdlin[i] == ' ') cmdlin[i] = '=';
}
char cmdlin2[256];
$DESCRIPTOR(cmdlin2desc, cmdlin2);
strcpy(cmdlin2, "CL1B ");
strcat(cmdlin2, cmdlin);
cmdlin2desc.dsc$w_length = strlen(cmdlin2);
cli$dcl_parse(&cmdlin2desc, cl1bsup);
char f[256];
int16_t flen;
$DESCRIPTOR(fnamedesc, "F");
$DESCRIPTOR(fdesc, f);
cli$get_value(&fnamedesc, &fdesc, &flen);
f[flen] = 0;
printf("C CL1B F=%s\n", f);
return 0;
}

$ set comm/obj cl1bsup
$ cc cl1b
$ link cl1b + cl1bsup
$ cl1b :== $sys$disk:[]cl1b
$ cl1b /f=z.z
C CL1B F=z.z
$ cl1b -f z.z
C CL1B F=z.z
$ type cl2.c
#include <stdio.h>
#include <string.h>
#include <stdint.h>

#include <descrip.h>
#include <lib$routines.h>

int main()
{
char cmdlin[256];
int16_t cmdlinlen;
$DESCRIPTOR(cmdlindesc, cmdlin);
lib$get_foreign(&cmdlindesc, 0, &cmdlinlen);
cmdlin[cmdlinlen] = 0;
char *p = strstr(cmdlin, "-f");
char f[256];
strcpy(f, p + 3);
printf("C CL2 F=%s\n", f);
return 0;
}

$ cc cl2
$ link cl2
$ cl2 :== $sys$disk:[]cl2
$ cl2 -f z.z
C CL2 F=z.z
$ type cl3.c
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

int main(int argc, char *argv[])
{
char *f = "";
bool fnext = false;
for(int i = 0; i < argc; i++)
{
if(fnext) f = argv[i];
fnext = strcmp(argv[i], "-f") == 0;
}
printf("C CL3 F=%s\n", f);
return 0;
}
$ cc cl3
$ link cl3
$ cl3 :== $sys$disk:[]cl3
$ cl3 -f z.z
C CL3 F=z.z

Python demo:

$ type cl3.py
import sys

f = ''
fnext = False
for arg in sys.argv:
if fnext:
f = arg
fnext = arg == '-f'
print(f"Python C3 F={f}")
$ python cl3.py -f z.z
Python C3 F=z.z

(the CL2 parsing examples are not robust, but that is another
topic)

Arne
Lawrence D'Oliveiro
2025-02-15 21:32:22 UTC
Reply
Permalink
Post by Mark Berryman
So, IMHO, DCL is superior in this regard.
Unfortunately, no. The fundamental problem with DEC OSes (and this
includes Windows) is that the command line is passed to the program as a
single string buffer. On *nix systems, it is passed as an array of
strings.

You should be familiar with the well-known problem of one program invoking
another with a command that might include characters with special meanings
to a shell. On a *nix system, there is a simple way to avoid those special
meanings: the first program invokes the second program directly, without
going through a shell.

Nowadays, there is even a simple library call to do this
<https://manpages.debian.org/posix_spawn(3)>.

This is not so easy to do with a DEC-style command line.
Arne Vajhøj
2025-02-16 01:21:12 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
Post by Mark Berryman
So, IMHO, DCL is superior in this regard.
Unfortunately, no. The fundamental problem with DEC OSes (and this
includes Windows) is that the command line is passed to the program as a
single string buffer. On *nix systems, it is passed as an array of
strings.
You should be familiar with the well-known problem of one program invoking
another with a command that might include characters with special meanings
to a shell. On a *nix system, there is a simple way to avoid those special
meanings: the first program invokes the second program directly, without
going through a shell.
Nowadays, there is even a simple library call to do this
<https://manpages.debian.org/posix_spawn(3)>.
This is not so easy to do with a DEC-style command line.
How do you get those "characters with special meanings
to a shell" interpreted instead of passed on VMS?

All my trivial attempts failed:

$ type self.pas
[inherit('sys$library:pascal$lib_routines', 'sys$library:starlet')]
program self(input,output);

[external]
function decc$system(%immed cmd : c_str_t) : integer; external;

type
pstr = varying [255] of char;

var
cmdlin : pstr;

begin
lib$get_foreign(cmdlin.body, , cmdlin.length);
if cmdlin <> '' then begin
writeln(cmdlin);
end else begin
decc$system(malloc_c_str('mcr sys$disk:[]self ''a'' ''b'' ''c'''));
lib$spawn('mcr sys$disk:[]self ''a'' ''b'' ''c''');
lib$do_command('mcr sys$disk:[]self ''a'' ''b'' ''c''');
end;
end.
$ pas self
$ link self
$ a = 1
$ b = 2
$ c = 3
$ mcr sys$disk:[]self 'a' 'b' 'c'
1 2 3
$ r self
'a' 'b' 'c'
'a' 'b' 'c'
'a' 'b' 'c'

$ type self2.c
#include <stdio.h>
#include <stdlib.h>

#include <descrip.h>
#include <lib$routines.h>

int main(int argc, char *argv[])
{
$DESCRIPTOR(cmddesc, "mcr sys$disk:[]self2 'a' 'b' 'c'");
if(argc > 1)
{
for(int i = 1; i < argc; i++) printf(" %s", argv[i]);
printf("\n");
}
else
{
system("mcr sys$disk:[]self2 'a' 'b' 'c'");
lib$spawn(&cmddesc);
lib$do_command(&cmddesc);
}
return 0;
}

$ cc self2
$ link self2
$ a = 1
$ b = 2
$ c = 3
$ mcr sys$disk:[]self 'a' 'b' 'c'
1 2 3
$ r self2
'a' 'b' 'c'
'a' 'b' 'c'
'a' 'b' 'c'

Arne
Mark Berryman
2025-02-16 22:52:23 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
Post by Mark Berryman
So, IMHO, DCL is superior in this regard.
Unfortunately, no. The fundamental problem with DEC OSes (and this
includes Windows) is that the command line is passed to the program as a
single string buffer. On *nix systems, it is passed as an array of
strings.
On *nix systems, the shell parses the command line into an array of
strings using unquoted spaces as the separator which is then passed to
the created process.

On VMS, the crtl does the same parsing which means the program still
sees an array of strings the same as on a *nix system.

If I choose to use DCL, DCL does all of the parsing for me and the
nature of the command-line is irrelevant.
Post by Lawrence D'Oliveiro
You should be familiar with the well-known problem of one program invoking
another with a command that might include characters with special meanings
to a shell. On a *nix system, there is a simple way to avoid those special
meanings: the first program invokes the second program directly, without
going through a shell.
Nowadays, there is even a simple library call to do this
<https://manpages.debian.org/posix_spawn(3)>.
This is not so easy to do with a DEC-style command line.
Incorrect. Programs on DEC OSes can invoke other programs directly and
have been able to at least since the days of the PDP-11 (which somewhat
predates the advent of posix_spawn). The nature of the command-line is
completely up to the program depending on what function is used to call
the new program.

Mark Berryman
Lawrence D'Oliveiro
2025-02-17 00:43:35 UTC
Reply
Permalink
Post by Mark Berryman
On *nix systems, the shell parses the command line into an array of
strings using unquoted spaces as the separator which is then passed to
the created process.
If you don’t go through a shell, then you pass an array of already-
separated words and you don’t have to worry about shell specials.
Post by Mark Berryman
On VMS, the crtl does the same parsing which means the program still
sees an array of strings the same as on a *nix system.
Consider what happens: if you pass unquoted text to program X, DCL
converts it to uppercase, and I think also normalizes multiple spaces to a
single space. If you don’t want the text to be uppercased or space-
normalized, you put it in pairs of double quotes. But then these double
quotes also get passed as part of the command line. So the receiving
program has to do some non-trivial parsing just to get simple literal text
via the command line.

So now, how do you invoke program X directly from your own program Y,
without going through DCL? For consistency, you have to mimic all the same
misbehaviour of DCL to get the command line in the right format. If you
don’t want to do that, then you need some option to tell program X to
bypass all that special processing, and just accept the command line as
is. So it needs to distinguish between two ways of being invoked: directly
or via DCL!
Post by Mark Berryman
If I choose to use DCL, DCL does all of the parsing for me and the
nature of the command-line is irrelevant.
If you don’t use DCL, then how do you invoke a program that expects DCL to
parse its command line for it? What happens to all of Arne’s lovingly-
crafted .CLD files?
Post by Mark Berryman
Programs on DEC OSes can invoke other programs directly and
have been able to at least since the days of the PDP-11 ...
Yes, and there have been quite a few pitfalls with that, as I mentioned
above. Windows suffers from the same drawback, and this has even led to
security holes with Windows programs.
Mark Berryman
2025-02-17 19:02:37 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
Post by Mark Berryman
On *nix systems, the shell parses the command line into an array of
strings using unquoted spaces as the separator which is then passed to
the created process.
If you don’t go through a shell, then you pass an array of already-
separated words and you don’t have to worry about shell specials.
Post by Mark Berryman
On VMS, the crtl does the same parsing which means the program still
sees an array of strings the same as on a *nix system.
Consider what happens: if you pass unquoted text to program X, DCL
converts it to uppercase, and I think also normalizes multiple spaces to a
single space. If you don’t want the text to be uppercased or space-
normalized, you put it in pairs of double quotes. But then these double
quotes also get passed as part of the command line. So the receiving
program has to do some non-trivial parsing just to get simple literal text
via the command line.
So, so, so very wrong. You are *way* behind the times.

I *never* have to quote arguments when using programs that still use
*nix syntax on VMS. My arguments' case is never changed.

Here is the entry point to any C program on VMS:

int main (int argc, char *argv[], char *envp[]);

See? Argument passing works the same on VMS as it does on *nix, as
described above.

Let's see, what's a good example? Ah, here's one:

$ gs -q -P- -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sstdout=%stderr
-sOutputFile=<something>.pdf <something>.ps

Again, see? No quoting. No case conversion. Ghostscript sees the
command exactly as I typed it and I typed it exactly as I would on a
*nix system.
Post by Lawrence D'Oliveiro
So now, how do you invoke program X directly from your own program Y,
without going through DCL? For consistency, you have to mimic all the same
misbehaviour of DCL to get the command line in the right format. If you
don’t want to do that, then you need some option to tell program X to
bypass all that special processing, and just accept the command line as
is. So it needs to distinguish between two ways of being invoked: directly
or via DCL!
Wrong again. There are a number of ways to invoke a program without
going through DCL. Since you seem to be a Linux fan, look up the
various exec functions for starters. (Note, there are other ways).
Post by Lawrence D'Oliveiro
Post by Mark Berryman
If I choose to use DCL, DCL does all of the parsing for me and the
nature of the command-line is irrelevant.
If you don’t use DCL, then how do you invoke a program that expects DCL to
parse its command line for it? What happens to all of Arne’s lovingly-
crafted .CLD files?
You apparently know as little about VMS as you do programming in
general. The Docs are online. Go read them. They will answer your
questions.
Post by Lawrence D'Oliveiro
Post by Mark Berryman
Programs on DEC OSes can invoke other programs directly and
have been able to at least since the days of the PDP-11 ...
Yes, and there have been quite a few pitfalls with that, as I mentioned
above. Windows suffers from the same drawback, and this has even led to
security holes with Windows programs.
Hmmm, first you tout that invoking programs directly eliminates pitfalls
then you claim that doing so causes them. So, which is it?

Mark Berryman
bill
2025-02-17 21:09:40 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
Post by Mark Berryman
On *nix systems, the shell parses the command line into an array of
strings using unquoted spaces as the separator which is then passed to
the created process.
If you don’t go through a shell, then you pass an array of already-
separated words and you don’t have to worry about shell specials.
Post by Mark Berryman
On VMS, the crtl does the same parsing which means the program still
sees an array of strings the same as on a *nix system.
Consider what happens: if you pass unquoted text to program X, DCL
converts it to uppercase, and I think also normalizes multiple spaces to a
single space. If you don’t want the text to be uppercased or space-
normalized, you put it in pairs of double quotes. But then these double
quotes also get passed as part of the command line. So the receiving
program has to do some non-trivial parsing just to get simple literal text
via the command line.
So, so, so very wrong.  You are *way* behind the times.
I *never* have to quote arguments when using programs that still use
*nix syntax on VMS.  My arguments' case is never changed.
 int main (int argc, char *argv[], char *envp[]);
See?  Argument passing works the same on VMS as it does on *nix, as
described above.
$ gs -q -P- -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sstdout=%stderr
-sOutputFile=<something>.pdf <something>.ps
Again, see?  No quoting.  No case conversion.  Ghostscript sees the
command exactly as I typed it and I typed it exactly as I would on a
*nix system.
Post by Lawrence D'Oliveiro
So now, how do you invoke program X directly from your own program Y,
without going through DCL? For consistency, you have to mimic all the same
misbehaviour of DCL to get the command line in the right format. If you
don’t want to do that, then you need some option to tell program X to
bypass all that special processing, and just accept the command line as
is. So it needs to distinguish between two ways of being invoked: directly
or via DCL!
Wrong again. There are a number of ways to invoke a program without
going through DCL.  Since you seem to be a Linux fan, look up the
various exec functions for starters.  (Note, there are other ways).
Post by Lawrence D'Oliveiro
Post by Mark Berryman
If I choose to use DCL, DCL does all of the parsing for me and the
nature of the command-line is irrelevant.
If you don’t use DCL, then how do you invoke a program that expects DCL to
parse its command line for it? What happens to all of Arne’s lovingly-
crafted .CLD files?
You apparently know as little about VMS as you do programming in
general.  The Docs are online.  Go read them.  They will answer your
questions.
Which is why people need to stop feeding the troll!!!!
Post by Lawrence D'Oliveiro
Post by Mark Berryman
Programs on DEC OSes can invoke other programs directly and
have been able to at least since the days of the PDP-11 ...
Yes, and there have been quite a few pitfalls with that, as I mentioned
above. Windows suffers from the same drawback, and this has even led to
security holes with Windows programs.
Hmmm, first you tout that invoking programs directly eliminates pitfalls
then you claim that doing so causes them.  So, which is it?
Mark Berryman
Robert A. Brooks
2025-02-17 21:42:10 UTC
Reply
Permalink
Post by bill
Post by Lawrence D'Oliveiro
Post by Mark Berryman
If I choose to use DCL, DCL does all of the parsing for me and the
nature of the command-line is irrelevant.
If you don’t use DCL, then how do you invoke a program that expects DCL to
parse its command line for it? What happens to all of Arne’s lovingly-
crafted .CLD files?
You apparently know as little about VMS as you do programming in general.  The
Docs are online.  Go read them.  They will answer your questions.
Which is why people need to stop feeding the troll!!!!
+1
--
--- Rob
Lawrence D'Oliveiro
2025-02-17 21:49:50 UTC
Reply
Permalink
Post by Mark Berryman
Post by Lawrence D'Oliveiro
Consider what happens: if you pass unquoted text to program X, DCL
converts it to uppercase, and I think also normalizes multiple spaces
to a single space. If you don’t want the text to be uppercased or
space- normalized, you put it in pairs of double quotes. But then these
double quotes also get passed as part of the command line. So the
receiving program has to do some non-trivial parsing just to get simple
literal text via the command line.
So, so, so very wrong. You are *way* behind the times.
I *never* have to quote arguments when using programs that still use
*nix syntax on VMS. My arguments' case is never changed.
Prove it. It seems to me what you are claiming would break backward
compatibility with the way VMS used to work.
Post by Mark Berryman
int main (int argc, char *argv[], char *envp[]);
See? Argument passing works the same on VMS as it does on *nix, as
described above.
$ gs -q -P- -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sstdout=%stderr
-sOutputFile=<something>.pdf <something>.ps
Again, see? No quoting. No case conversion. Ghostscript sees the
command exactly as I typed it and I typed it exactly as I would on a
*nix system.
Can you show us a simple C program that just prints out its command
arguments, and how it responds to some sample command lines?
jayjwa
2025-02-17 23:46:49 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
Can you show us a simple C program that just prints out its command
arguments, and how it responds to some sample command lines?
VMS seems to be alone in converting case.

✔≻ cat cmdlin.c
/* A program to demo printing of command line args under various OSs. */
#include <stdio.h>


int main( int argc, char *argv[] ) {
printf( "Did I hear you say %s?\n", argv[1] );
return 0;
}
✔≻ gcc -o cmdlin cmdlin.c
✔≻ ./cmdlin Hello World
Did I hear you say Hello?
✔≻ ./cmdlin "Hello World"
Did I hear you say Hello World?

@compile cmdlin.c
KCC: CMDLIN
"CMDLIN.C", line 9: [Note] Parameter "argc" not used
(main+4, p.1 l.8): "Did I hear you say %s?\n", argv[1] ); return 0; }

<JAYJWA.PROGRAMMING>CMDLIN.PRE.1
<JAYJWA.PROGRAMMING>CMDLIN.FAI.1
FAIL: CMDLIN
@load cmdlin
LINK: Loading
@save cmdlin
CMDLIN.EXE.2 Saved
@cmdlin Hello World
Did I hear you say Hello?
@cmdlin "Hello World"
Did I hear you say Hello World?

c:\bcc32 -5 -ecmdlin.exe cmdlin.c
c:\cmdlin Hello World
Did I hear you say Hello?
c:\cmdlin "Hello World"
Did I hear you say Hello World?

$ cc /version
Compaq C V6.4-005 on OpenVMS VAX V7.3
$ cc cmdlin.c
$ link cmdlin
$ mcr DUA1:[JAYJWA.PROGRAMMING.C]cmdlin Hello World
Did I hear you say hello?
$ mcr DUA1:[JAYJWA.PROGRAMMING.C]cmdlin "Hello World"
Did I hear you say Hello World?

This matters with tools like curl (-O vs -o) and zip (-V and -v).
--
PGP Key ID: 781C A3E2 C6ED 70A6 B356 7AF5 B510 542E D460 5CAE
"The Internet should always be the Wild West!"
Arne Vajhøj
2025-02-18 00:20:57 UTC
Reply
Permalink
Post by jayjwa
VMS seems to be alone in converting case.
✔≻ cat cmdlin.c
/* A program to demo printing of command line args under various OSs. */
#include <stdio.h>
int main( int argc, char *argv[] ) {
printf( "Did I hear you say %s?\n", argv[1] );
return 0;
}
$ cc /version
Compaq C V6.4-005 on OpenVMS VAX V7.3
$ cc cmdlin.c
$ link cmdlin
$ mcr DUA1:[JAYJWA.PROGRAMMING.C]cmdlin Hello World
Did I hear you say hello?
$ mcr DUA1:[JAYJWA.PROGRAMMING.C]cmdlin "Hello World"
Did I hear you say Hello World?
Yeah. But look at the calendar.

$ mcr []cmdlin Hello World
Did I hear you say Hello?
$ set proc/pars=trad
$ mcr []cmdlin Hello World
Did I hear you say hello?
$ set proc/pars=ext
$ mcr []cmdlin Hello World
Did I hear you say Hello?

Arne
Lawrence D'Oliveiro
2025-02-18 01:19:55 UTC
Reply
Permalink
Post by jayjwa
$ mcr DUA1:[JAYJWA.PROGRAMMING.C]cmdlin Hello World
Did I hear you say hello?
That demonstrates, does it not, that the C runtime is doing its own
processing of the command line buffer, to try to mimic POSIX behaviour.
Which proves my point, does it not.

That probably includes stripping of quotes as well.
Lawrence D'Oliveiro
2025-02-18 02:12:00 UTC
Reply
Permalink
Post by jayjwa
$ mcr DUA1:[JAYJWA.PROGRAMMING.C]cmdlin Hello World
Did I hear you say hello?
What you forgot to do is SET PROCESS/PARSE_STYLE=EXTENDED. I think this
was added in VMS v7.2. It does case preservation and disables
interpretation of some special characters, but there is no mention that I
can see of what it does to quotation marks.

OK, the proper test would be a simple program, maybe not in C, that does a
LIB$GET_FOREIGN and shows the results of that. Then we can compare giving
it different command lines and seeing what happens to them.

Basically, the whole thing smells like a gigantic hack to try to bring
things a little closer to POSIX behaviour. I doubt it’s very successful.
Arne Vajhøj
2025-02-18 00:38:57 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
Post by Mark Berryman
Post by Lawrence D'Oliveiro
Consider what happens: if you pass unquoted text to program X, DCL
converts it to uppercase, and I think also normalizes multiple spaces
to a single space. If you don’t want the text to be uppercased or
space- normalized, you put it in pairs of double quotes. But then these
double quotes also get passed as part of the command line. So the
receiving program has to do some non-trivial parsing just to get simple
literal text via the command line.
So, so, so very wrong. You are *way* behind the times.
I *never* have to quote arguments when using programs that still use
*nix syntax on VMS. My arguments' case is never changed.
Prove it. It seems to me what you are claiming would break backward
compatibility with the way VMS used to work.
Post by Mark Berryman
int main (int argc, char *argv[], char *envp[]);
See? Argument passing works the same on VMS as it does on *nix, as
described above.
$ gs -q -P- -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sstdout=%stderr
-sOutputFile=<something>.pdf <something>.ps
Again, see? No quoting. No case conversion. Ghostscript sees the
command exactly as I typed it and I typed it exactly as I would on a
*nix system.
Can you show us a simple C program that just prints out its command
arguments, and how it responds to some sample command lines?
Why would we do that?

Last time you came with those claims I posted (23-May-2024)
a rather extensive demo with CLI, foreign command, hybrid, C
and DCL with both /parse=ext and /parse=trad.

Apparently you did not read it. Which is fine, but don't expect
anybody to redo the work when you did not read it the first time.

Arne
Lawrence D'Oliveiro
2025-02-18 01:21:24 UTC
Reply
Permalink
Post by Arne Vajhøj
Post by Lawrence D'Oliveiro
Post by Mark Berryman
$ gs -q -P- -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sstdout=%stderr
-sOutputFile=<something>.pdf <something>.ps
Again, see? No quoting. No case conversion. Ghostscript sees the
command exactly as I typed it and I typed it exactly as I would on a
*nix system.
Can you show us a simple C program that just prints out its command
arguments, and how it responds to some sample command lines?
Why would we do that?
To prove that Mr Berryman is blowing smoke out his arse.
Mark Berryman
2025-02-18 02:23:07 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
Post by Mark Berryman
Post by Lawrence D'Oliveiro
Consider what happens: if you pass unquoted text to program X, DCL
converts it to uppercase, and I think also normalizes multiple spaces
to a single space. If you don’t want the text to be uppercased or
space- normalized, you put it in pairs of double quotes. But then these
double quotes also get passed as part of the command line. So the
receiving program has to do some non-trivial parsing just to get simple
literal text via the command line.
So, so, so very wrong. You are *way* behind the times.
I *never* have to quote arguments when using programs that still use
*nix syntax on VMS. My arguments' case is never changed.
Prove it. It seems to me what you are claiming would break backward
compatibility with the way VMS used to work.
Post by Mark Berryman
$ gs -q -P- -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sstdout=%stderr
-sOutputFile=<something>.pdf <something>.ps
I'm pretty sure this just did.

And no, no breakage. Each user on each VMS system can choose how they
want to things to work. I could easily set things up to work the way
you think they should work but that would be so '90s; pretty much what
the poster from VAX/VMS showed. I prefer to operate in the 21st century
and take advantage of what VMS offers today, something of which you have
proven yourself completely ignorant.
Post by Lawrence D'Oliveiro
.
.
.
Can you show us a simple C program that just prints out its command
arguments, and how it responds to some sample command lines?
Easily.

$ type proof.c
#include <stdio.h>

int main(int argc, char *argv[])
{
for (int i=1; i<argc; i++)
printf("%s\n", argv[i]);
}


$ mcr []proof These Are The Arguments
These
Are
The
Arguments

$ mcr []proof These Are The "Arguments"
These
Are
The
Arguments


Now, please crawl back into your hole. Trolls don't do well in sunlight
and your extreme ... obtuseness ... is showing.

Everyone else: Yes, I've once again been forced to face the fact that
you can't fix stupid. I won't be responding anymore.

Mark Berryman
Lawrence D'Oliveiro
2025-02-19 01:58:52 UTC
Reply
Permalink
Post by Mark Berryman
Post by Lawrence D'Oliveiro
Can you show us a simple C program that just prints out its command
arguments, and how it responds to some sample command lines?
Easily.
Try some more subtle examples. On a proper POSIX-type system:

QUOTE='"'
./test "Hello World" ${QUOTE}Hello World${QUOTE}
Hello World
"Hello
World"

Try to get the equivalent in DCL:

quote = """"
«rigmarole equivalent to ./test» "Hello World" 'quote'Hello World'quote'

What’s it going to print?
Arne Vajhøj
2025-02-19 02:56:51 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
Post by Mark Berryman
Post by Lawrence D'Oliveiro
Can you show us a simple C program that just prints out its command
arguments, and how it responds to some sample command lines?
Easily.
QUOTE='"'
./test "Hello World" ${QUOTE}Hello World${QUOTE}
Hello World
"Hello
World"
quote = """"
«rigmarole equivalent to ./test» "Hello World" 'quote'Hello World'quote'
What’s it going to print?
This is more of a shell question than an OS question.

bash and DCL are different.

$ bash
bash-4.4$ QUOTE='"'
bash-4.4$ ./test "Hello World" ${QUOTE}Hello World${QUOTE}
Hello World
"Hello
World"
bash-4.4$ exit
exit

$ quote = """"""
$ mcr sys$disk:[]test "Hello World" "''quote'"Hello World"''quote'"
Hello World
"Hello
World"

Arne
Dan Cross
2025-02-19 03:03:16 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
Post by Lawrence D'Oliveiro
Post by Mark Berryman
Post by Lawrence D'Oliveiro
Can you show us a simple C program that just prints out its command
arguments, and how it responds to some sample command lines?
Easily.
QUOTE='"'
./test "Hello World" ${QUOTE}Hello World${QUOTE}
Hello World
"Hello
World"
quote = """"
«rigmarole equivalent to ./test» "Hello World" 'quote'Hello
World'quote'
Post by Lawrence D'Oliveiro
What’s it going to print?
This is more of a shell question than an OS question.
bash and DCL are different.
$ bash
bash-4.4$ QUOTE='"'
bash-4.4$ ./test "Hello World" ${QUOTE}Hello World${QUOTE}
Hello World
"Hello
World"
bash-4.4$ exit
exit
$ quote = """"""
$ mcr sys$disk:[]test "Hello World" "''quote'"Hello World"''quote'"
Hello World
"Hello
World"
I am utterly baffled as to why you continue to regularly
engage with this troll. It's your choice, of course, but
getting the backsplatter is unpleasant for rest of us who
have already plonked him.

- Dan C.
Robert A. Brooks
2025-02-19 04:24:53 UTC
Reply
Permalink
Post by Dan Cross
I am utterly baffled as to why you continue to regularly
engage with this troll. It's your choice, of course, but
getting the backsplatter is unpleasant for rest of us who
have already plonked him.
+1!
--
--- Rob
Arne Vajhøj
2025-02-19 21:06:10 UTC
Reply
Permalink
Post by Arne Vajhøj
Post by Dan Cross
I am utterly baffled as to why you continue to regularly
engage with this troll.  It's your choice, of course, but
getting the backsplatter is unpleasant for rest of us who
have already plonked him.
+1!
I have a different perspective.
Post by Arne Vajhøj
$ bash
bash-4.4$ QUOTE='"'
bash-4.4$ ./test "Hello World" ${QUOTE}Hello World${QUOTE}
Hello World
"Hello
World"
bash-4.4$ exit
exit
(using VSI's recent VMS x86-64 GNV kit)

may not be something used in the real world, but it does raise the
question of how to do it in DCL.
Post by Arne Vajhøj
$ quote = """"""
$ mcr sys$disk:[]test "Hello World" "''quote'"Hello World"''quote'"
Hello World
"Hello
World"
There may be better solutions.

(if anyone knows one please post!)

But it is definitely on topic for c.o.v/I-V.

And maybe (just maybe) it is useful for a current or a future reader.

I do not care much about who raised the question. It doesn't change
being on topic or potential usefulness.

Arne
Dan Cross
2025-02-19 21:53:17 UTC
Reply
Permalink
Post by Arne Vajhøj
Post by Dan Cross
I am utterly baffled as to why you continue to regularly
engage with this troll.  It's your choice, of course, but
getting the backsplatter is unpleasant for rest of us who
have already plonked him.
+1!
I have a different perspective.
[snip]
But it is definitely on topic for c.o.v/I-V.
And maybe (just maybe) it is useful for a current or a future reader.
I do not care much about who raised the question. It doesn't change
being on topic or potential usefulness.
Be that as it may, the person you are responding to, and that
you regularly interact with at length, has shown repeatedly that
he is not acting in good faith.

Technical answers to technical questions, and even spirited
debates with vigorous disagreement, are all well and good as
long as all parties are at least attempting to honor a collegial
spirit of cooperation. But in this case, as with most cases
involving Lawrence, the base conditions in which to have the
discussion are simply not met.

This is the sort of thing that ought to be in an FAQ.

- Dan C.
Lawrence D'Oliveiro
2025-02-19 23:43:10 UTC
Reply
Permalink
Post by jayjwa
Post by Arne Vajhøj
$ quote = """"""
$ mcr sys$disk:[]test "Hello World" "''quote'"Hello World"''quote'"
Hello World
"Hello
World"
Did you notice that DCL (and therefore the receiving program) cannot tell
the difference between substituting the “quote” variable versus entering a
literal quote?

Lawrence D'Oliveiro
2025-02-19 23:20:11 UTC
Reply
Permalink
Post by Arne Vajhøj
This is more of a shell question than an OS question.
bash and DCL are different.
It’s a fundamental difference in how the OSes handle the command line.

Remember, the DEC model (and also that of Microsoft Windows) is that of a
simple string buffer. The POSIX model is an array of strings (words).

To emulate the POSIX way of doing things when a program A spawns a program
B without going through the shell, in the DEC/Microsoft model program A
has to encode the words as though they were being fed to a shell. And
correspondingly, program B has to decode the result in the same way, as
though it were receiving the command as a shell. This is a complex and
fragile process, with ample opportunities for error.

Try that example again, the way I wrote it, and you should get a better
idea of what I mean.
Loading...