• Why do I need to "join" it?

    From Kenny McCormack@21:1/5 to All on Sat May 14 11:27:23 2022
    I have an Expect program that takes a single command line parameter, which
    is a filename (which must exist). The program starts with:

    #!/usr/bin/expect --
    if {$argc != 1 || ![file exists $argv]} { puts "Error";exit 1 }

    This works fine as long as the filename is "normal" - i.e., doesn't contain
    any spaces (and/or perhaps other weird characters). However, if it has a space, then the "file exists" check fails, because the actual value of
    $argv is something like:

    {/path/to/file name with spaces}

    The curly braces foo up the "file exists" check. I am able to workaround
    this by adding the following line before the above "if" statement:

    set argv [join $argv]

    And this seems to be the fix. As far as I can tell, the above line doesn't break anything. In fact, I've used this kludge/hack in the past, so I
    didn't have to invent it anew today. But why is it necessary? Isn't it
    weird that it is necessary?
    1
    --
    In the corner of the room on the ceiling is a large vampire bat who
    is obviously deranged and holding his nose.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From saitology9@gmail.com@21:1/5 to Kenny McCormack on Sat May 14 14:52:20 2022
    On 5/14/22 7:27 AM, Kenny McCormack wrote:
    I have an Expect program that takes a single command line parameter, which
    is a filename (which must exist). The program starts with:

    #!/usr/bin/expect --
    if {$argc != 1 || ![file exists $argv]} { puts "Error";exit 1 }

    ...

    {/path/to/file name with spaces}

    The curly braces foo up the "file exists" check. I am able to workaround this by adding the following line before the above "if" statement:

    set argv [join $argv]

    And this seems to be the fix. As far as I can tell, the above line doesn't break anything. In fact, I've used this kludge/hack in the past, so I
    didn't have to invent it anew today. But why is it necessary? Isn't it weird that it is necessary?


    Hello,

    It is not weird, it is documented and is pretty common in some other
    languages too. While you may have gotten away with it in the past, it
    is purely due to the number and nature of the arguments you have worked
    with. The code is buggy. Another latent error in your code which you
    haven't seen yet is that you should use lindex with argv.


    "argv" is handled as a list. I don't have it handy at the moment but
    the proc man page should have a nice description on it: any and all
    remaining (unnamed) arguments from the command line are collected, put
    into a list, and provided to the proc as a list named "argv". If you
    have spaces within an individual argument's data, you would need to
    escape that somehow in the command line. There may be no way to
    properly reconstruct the original argument data in the proc otherwise
    (think of multiple file names with spaces in them).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kenny McCormack@21:1/5 to saitology9@gmail.com on Sat May 14 20:05:20 2022
    In article <t5otpa$s5p$1@gioia.aioe.org>, <saitology9@gmail.com> wrote:
    ...
    Hello,

    Thank you for your response.

    However, it does not tell me anything I didn't already know.

    But thank you anyway.

    P.S. Yes, it is weird. Why is join needed? I.e., am I correct in
    assuming that it only puts the {} in, when there is a space (or some other "weird" character) in the filename?


    --

    "This ain't my first time at the rodeo"

    is a line from the movie, Mommie Dearest, said by Joan Crawford at a board meeting.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From mango@21:1/5 to Kenny McCormack on Sat May 14 13:46:22 2022
    On Saturday, May 14, 2022 at 4:27:28 AM UTC-7, Kenny McCormack wrote:
    I have an Expect program that takes a single command line parameter, which
    is a filename (which must exist). The program starts with:

    #!/usr/bin/expect --
    if {$argc != 1 || ![file exists $argv]} { puts "Error";exit 1 }

    [snip]

    Since argv is a list, the proper way to obtain the first element is, [lindex $argv 0].

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From saitology9@gmail.com@21:1/5 to Kenny McCormack on Sat May 14 17:35:19 2022
    On 5/14/22 4:05 PM, Kenny McCormack wrote:
    In article <t5otpa$s5p$1@gioia.aioe.org>, <saitology9@gmail.com> wrote:
    ...
    Hello,

    Thank you for your response.

    However, it does not tell me anything I didn't already know.


    Hello,

    I re-read what I wrote and I believe there is a lot of relevant info
    there on argv, how it is constructed, and what the ramifications are for passing data to proc's as unnamed arguments via argv. You may or may
    not know this but the code you gave shows a lack of it in practice.

    "argv" is a list and it is made up of whatever is found on the command
    line after the named arguments of the target procedure. Therefore, you
    need to expect a list in it and use list commands to process it.

    if {$argc != 1 || ![file exists $argv]} { puts "Error";exit 1 }

    You can't use argv as the name of the file.



    But thank you anyway.

    P.S. Yes, it is weird. Why is join needed? I.e., am I correct in
    assuming that it only puts the {} in, when there is a space (or some other "weird" character) in the filename?



    Join is needed to convert a list of items into a single string which you
    later use as file name. You can't use a list as a literal value (nowwithstanding Tcl's lax treatment of single atomic element lists).

    Please look up argv in the manuals. Also try to extend your proc above
    to accept two file names (which may contain spaces) using the argv
    convention. This may be more beneficial.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From briang@21:1/5 to Kenny McCormack on Sat May 14 14:24:00 2022
    On Saturday, May 14, 2022 at 1:05:25 PM UTC-7, Kenny McCormack wrote:
    In article <t5otpa$s5p$1...@gioia.aioe.org>, <saito...@gmail.com> wrote:
    ...
    Hello,

    Thank you for your response.

    However, it does not tell me anything I didn't already know.

    But thank you anyway.

    P.S. Yes, it is weird. Why is join needed? I.e., am I correct in
    assuming that it only puts the {} in, when there is a space (or some other "weird" character) in the filename?

    It is not weird! It is as designed.
    As has been repeated here already, argv is a list. It is NOT a scalar value. Your code already asserts that argc == 1. This value should always equal the list length of argv: [llength $argv] == $argc
    If you put 2 arguments on the command line, argc will be 2, and your test will error as it should. The llength of $argv will also be 2.

    Since argv is always a list, the code must index elements from the list, even if there is only one element. The weird part, if any, is that your code just happens to work for the subset of filenames without spaces. It happens to work because the script
    has a bug, i.e., not using [lindex].

    Lastly, it is faulty to assume that the kind of a variable can be determined by looking at the value. This is false. The kind of a variable (i.e. list, scalar, dict, etc.) is determined by how it is used in the script. The argv variable is created by
    tclsh as a list, therefore, any subsequent operations on it must be list operations.

    I've written this response in a somewhat hash tone, sorry. I do this because unless this fact about list values vs scalar values is deeply understood, programs written in Tcl will continue to be buggy. Code written to respect the "kind" of data will
    always be correct, at least with respect to manipulation of the datum.

    -Brian

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From clt.to.davebr@dfgh.net@21:1/5 to All on Sun May 15 06:00:58 2022
    From: gazelle@shell.xmission.com (Kenny McCormack)
    Date: Sat May 14 11:27:23 GMT 2022
    Subject: Why do I need to "join" it?


    is a filename (which must exist). The program starts with:

    #!/usr/bin/expect --
    if {$argc != 1 || ![file exists $argv]} { puts "Error";exit 1 }

    This works fine as long as the filename is "normal" - i.e., doesn't contain >any spaces (and/or perhaps other weird characters). However, if it has a >space, then the "file exists" check fails, because the actual value of
    $argv is something like:

    {/path/to/file name with spaces}


    I'm confused, when I remove argc!=1 test in the if statement, the file exists part seems to work for file names with or without spaces, as long as spaces are separated by non-space characters. If two or more spaces are next to each other, it will fail (
    or use the wrong file if there is one where all runs of multiple spaces in the name are replaced by single spaces).

    As noted in other replies above it is tacky, but it does work (for files with no runs of multiple spaces in their names)

    If you want to handle really weird file names, enclose any odd file name in quotes on the command line:

    expect-script "/path/to/file name with spaces"

    This ends up wrapping the file name in braces if it includes any spaces when $argv is used as the file name. Because expect (TCL) knows the "file exists" command wants a string, it converts the list in argv to a string by separating each list entry with
    a single space, and encloses any entries with spaces in braces {}. This is done so TCL can regenerate the original list from the string (not something we need or want here).

    However saving the first (and only) list entry in argv back to argv in the script (as below) solves that problem, because argv now contains only the first entry in the list (which is a string, not a list so TCL can pass it directly to the file exists
    command).

    #!/usr/bin/expect --
    set argv [lindex $argv 0]
    if {$argc != 1 || ![file exists $argv]} { puts "Error";exit 1 }


    Dave B

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From saitology9@gmail.com@21:1/5 to clt.to.davebr@dfgh.net on Sun May 15 13:34:56 2022
    On 5/15/22 2:00 AM, clt.to.davebr@dfgh.net wrote:

    I'm confused, when I remove argc!=1 test in the if statement, the file exists part seems to work for file names with or without spaces, as long as spaces are separated by non-space characters. If two or more spaces are next to each other, it will fail (
    or use the wrong file if there is one where all runs of multiple spaces in the name are replaced by single spaces).


    Hello,

    The single vs multiple space thing is just the standard way the command
    line processors work. They ignore spaces between arguments and pass them
    to the target program/script separated with single spaces. By the way,
    this is also how Tcl's own [list] command works. For example, "[list a
    b]" is identical to "[list a b]". The intermediate spaces have
    no meaning. If you want to preserve multiple spaces in an argument, you
    will need to take special care by using whatever the escape mechanism is
    for the command line processor: single quotes, double quotes, escape characters, etc.

    The reason the original code seems to work for simple file names is
    Tcl's stringification of single and simple element lists. For example,
    the string version of scalar "a" and list "[list a]" are identical.
    However, if the scalar value has spaces or special characters in them,
    or when the list contains multiple elements, the stringification gives different results in order to reflect the original form.


    As noted in other replies above it is tacky, but it does work (for files with no runs of multiple spaces in their names)

    If you want to handle really weird file names, enclose any odd file name in quotes on the command line:

    expect-script "/path/to/file name with spaces"


    This is the proper way to (always) distinguish arguments in a command
    line. Some shells will do this for you automatically through
    auto-completion when the need arises, but otherwise, most people just
    ignore the convention because their command invocations usually contain
    simple scalar values only.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ralf Fassel@21:1/5 to All on Mon May 16 10:29:52 2022
    * gazelle@shell.xmission.com (Kenny McCormack)
    | I have an Expect program that takes a single command line parameter,
    | which is a filename (which must exist). The program starts with:

    | #!/usr/bin/expect --
    | if {$argc != 1 || ![file exists $argv]} { puts "Error";exit 1 }

    | This works fine as long as the filename is "normal" - i.e., doesn't contain
    | any spaces (and/or perhaps other weird characters). However, if it has a
    | space, then the "file exists" check fails, because the actual value of
    | $argv is something like:

    | {/path/to/file name with spaces}

    | The curly braces foo up the "file exists" check. I am able to workaround
    | this by adding the following line before the above "if" statement:

    | set argv [join $argv]

    | And this seems to be the fix.

    Wrong fix. As you know, argv is a list. If you use $argv in a string
    context (as in 'file exists', which expects a single argument filename),
    the string representation of that list is substituted. And if the list contains spaces, TCL needs to quote these spaces in case you want to
    re-use the string representation as a list again - this is why the "{}"
    creep in.

    To access the individual elements of a list, you need to use lindex:

    set filename [lindex $argv 0]
    set second_arg [lindex $argv 1]
    set third_arg [lindex $argv 2]

    # or alternatively all in one go:
    lassign $argv filename second_arg third_arg

    if {![file exists $filename]} ...


    Using 'join' on a list just joins the individual parts of the list with
    spaces, which sometimes works for filenames. But as you have noted, in
    other cases (double space in filename) it fails, so this is not a
    solution.

    Also, just printing 'Error' in case of failure is not very helpful to
    the end-user (which might be you in three months, wondering what 'Error'
    might mean). Just add "input file '$filename' does not exist" to the
    error message.

    HTH
    R'

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)