[Shell] Backslash within double quotes #YT5

I’m stuck on Stage #GU3

My code passes GU3, and regression for LE5, but fails on YT5. I’ve reworked the way I parse arguments, so it might be my fault, but I’m curious about logs:

remote: [tester::#YT5] Writing file "/tmp/quz/f\n55" with content "grape pineapple."
remote: [tester::#YT5] Writing file "/tmp/quz/f\69" with content "strawberry orange."
remote: [tester::#YT5] Writing file "/tmp/quz/f'\'19" with content "orange strawberry."
remote: [your-program] $ cat "/tmp/quz/f\n55" "/tmp/quz/f\69" "/tmp/quz/f'\'19"
remote: [your-program] /usr/bin/cat: '/tmp/quz/f'$'\n''55': No such file or directory
remote: [tester::#YT5] Output does not match expected value.
remote: [tester::#YT5] Expected: "grape pineapple.strawberry orange.orange strawberry."
remote: [tester::#YT5] Received: "/usr/bin/cat: '/tmp/quz/f'$'\n''55': No such file or directory"
remote: [your-program] strawberry orange.orange strawberry.
remote: [your-program] cat execution error: Command failed with exit code 1

when I try to this input (replace cat with echo), I get the next output, and it seems rights

$ echo "/tmp/quz/f\n55" "/tmp/quz/f\69" "/tmp/quz/f'\'19"
/tmp/quz/f
55 /tmp/quz/f\69 /tmp/quz/f'\'19
$ 

so I need a little explanation about what is real input for this test or might u have issue in tester

if we speak about Stage #YT5 (Quoting - Backslash outside quotes) then it works as expected or not?:

$ echo /tmp/quz/f\n55 /tmp/quz/f\69 /tmp/quz/f'\'19       
/tmp/quz/fn55 /tmp/quz/f69 /tmp/quz/f'19
$ 

here my tests:

#[cfg(test)]
mod tests {
    #[test]
    fn test_parse_basic() {
        assert_eq!(super::parse(""), Vec::<String>::new());
        assert_eq!(super::parse("arg1 arg2 arg3"), vec!["arg1", "arg2", "arg3"]);
        assert_eq!(super::parse("  arg1   arg2  "), vec!["arg1", "arg2"]);
    }

    #[test]
    fn test_parse_with_single_quotes() {
        assert_eq!(
            super::parse("'only one quoted arg'"),
            vec!["only one quoted arg"]
        );

        assert_eq!(
            super::parse("'/tmp/file name' '/tmp/file name with spaces'"),
            vec!["/tmp/file name", "/tmp/file name with spaces"]
        );

        assert_eq!(
            super::parse("'/tmp/file name' yahoo '/tmp/file name with spaces'"),
            vec!["/tmp/file name", "yahoo", "/tmp/file name with spaces"]
        );
    }

    #[test]
    fn test_parse_with_double_quotes() {
        assert_eq!(
            super::parse(r#""only one quoted arg""#),
            vec!["only one quoted arg"]
        );

        assert_eq!(
            super::parse(r#""/tmp/file name" "/tmp/file name with spaces""#),
            vec!["/tmp/file name", "/tmp/file name with spaces"]
        );

        assert_eq!(
            super::parse(r#""/tmp/file name" yahoo "/tmp/file name with spaces""#),
            vec!["/tmp/file name", "yahoo", "/tmp/file name with spaces"]
        );

        assert_eq!(
            super::parse(r#""/tmp/qux/"f 96""#),
            vec!["/tmp/qux/f", "96"]
        )
    }

    #[test]
    fn test_parse_with_mixed_quotes_1() {
        assert_eq!(
            super::parse(r#""'only one quoted arg'""#),
            vec!["'only one quoted arg'"]
        );

        assert_eq!(
            super::parse(r#"'"only one quoted arg"'"#),
            vec!["\"only one quoted arg\""]
        );

        assert_eq!(
            super::parse(r#"'"/tmp/file name"' "'/tmp/file name with spaces'""#),
            vec![r#""/tmp/file name""#, "'/tmp/file name with spaces'"]
        );
    }

    #[test]
    fn test_parse_with_mixed_quotes_2() {
        assert_eq!(
            super::parse(r#""'only" one "quoted arg'""#),
            vec!["'only", "one", "quoted arg'"]
        );
    }

    #[test]
    fn test_parse_with_backslashes_outside_quotes() {
        assert_eq!(
            super::parse(r#"world\ \ \ \ \ \ script"#),
            vec!["world      script"]
        );
        assert_eq!(super::parse(r#"before\nafter"#), vec!["beforenafter"]);
        assert_eq!(
            super::parse(r#"\'\"script world\"\'"#),
            vec![r#"'"script"#, r#"world"'"#]
        );
    }

    #[test]
    fn test_parse_with_backslashes_within_single_quotes() {
        assert_eq!(
            super::parse("'shell\\\\\\nscript'"),
            vec!["shell\\\\\\nscript"]
        );
        assert_eq!(
            super::parse("'example\\\"testhello\\\"shell'"),
            vec!["example\\\"testhello\\\"shell"]
        );
    }

    #[test]
    fn test_parse_with_backslashes_within_double_quotes() {
        assert_eq!(
            super::parse(r#""hello\"insidequotes"script\""#),
            vec![r#"hello"insidequotesscript""#]
        );
        assert_eq!(super::parse(r#""/tmp/bar/f\n99""#), vec!["/tmp/bar/f\n99"]);
    }
}

I found one issue: my code consumes opening quotes when parsing a ‘basic’ string. Let’s see the results after I fix it.

Hi @stanykey, let us know how the fix works out!

I fixed behavior for Stage #YT5 (Quoting - Backslash outside quotes)

$ echo /tmp/quz/f\n55 /tmp/quz/f\69 /tmp/quz/f'\'19
/tmp/quz/fn55 /tmp/quz/f69 /tmp/quz/f\19
$ echo 'before'"after" test'\\\\\'"19"
beforeafter test\\\\\19
$

but the tester fails my code on the ‘same’ test

remote: [tester::#YT5] Writing file "/tmp/qux/f\n73" with content "strawberry banana."
remote: [tester::#YT5] Writing file "/tmp/qux/f\43" with content "strawberry orange."
remote: [tester::#YT5] Writing file "/tmp/qux/f'\'97" with content "pineapple blueberry."
remote: [your-program] $ cat "/tmp/qux/f\n73" "/tmp/qux/f\43" "/tmp/qux/f'\'97"
remote: [your-program] /usr/bin/cat: '/tmp/qux/f'$'\n''73': No such file or directory
remote: [tester::#YT5] Output does not match expected value.
remote: [tester::#YT5] Expected: "strawberry banana.strawberry orange.pineapple blueberry."
remote: [tester::#YT5] Received: "/usr/bin/cat: '/tmp/qux/f'$'\n''73': No such file or directory"
remote: [your-program] strawberry orange.pineapple blueberry.
remote: [your-program] cat execution error: Command failed with exit code 1
remote: [your-program] $

here are my outputs for the problematic path:

$ echo /tmp/qux/f\n73
/tmp/qux/fn73
$ echo "/tmp/qux/f\n73" 
/tmp/qux/f
73
$ echo '/tmp/qux/f\n73' 
/tmp/qux/f\n73
$ 

my results are equal to the native zsh outputs, so we are back to the initial statement:

  • or the tester doesn’t show original input, and I can’t figure out where an issue in my code
  • or the tester has a bug

I guess the real input somethin like this

echo /tmp/qux/f\\n73

right? if yes then the test should be like this:

assert_eq!(super::parse(r#"file\\n19"#), vec!["file\n19"]);

right?

@andy1li could you help with input?

I don’t want to watch examples of code since I want to try to figure it out myself, but it looks like I need to do two-pass parsing, which probably today shells do by default without explicit the -e option

@stanykey The issue seems related to how std::process::Command expects newlines to be passed in.

Right now your code is interpreting "\n" (a backslash and n) as actual newlines.

If newlines were converted back to "\\n" (a backslash and n), the tests could pass successfully:

Oh, I forgot about the fact that system::Command also does similar processing for args

Thanks for the help, @andy1li

1 Like

I have a question regarding the same. Shouldn’t the \n when in double quotes be treated as a new line? I believe the first path to be an invalid path. My code is also failing on the same test case.

EDIT: I know this screenshot is for an earlier stage(#GU3) but with reference to that wouldn’t the \n when surrounded with double quotes be treated a newline for the test case here?

According to documentation, it’s treated as a new line if it’s quoted (no matter whether single or double). In my case, system::Command::args did second pass processing of the input string, and it caused the issue with the cat call, so check how you pass args because u may see the right result with echo but then have the wrong with external command due to some API also does argument processing

1 Like

Hi @pnicto, I agree that \n should be treated as a newline, but the key question is who should handle that interpretation.

In Go, the responsibility might lies with exec.Command. Since it directly executes the provided arguments, we need to respect how it expects those arguments to be passed.

Specifically, if exec.Command expects a backslash followed by n (ASCII: 92 and 110) but receives a line feed (ASCII: 10), it could lead to unexpected behavior.

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.