]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
xgettext: Shell: Recognize \c escape sequences in $'...' strings.
authorBruno Haible <bruno@clisp.org>
Sat, 21 Jun 2025 00:53:18 +0000 (02:53 +0200)
committerBruno Haible <bruno@clisp.org>
Sat, 21 Jun 2025 00:53:18 +0000 (02:53 +0200)
* gettext-tools/src/x-sh.c (read_word): Recognize \c escape sequences.
* gettext-tools/tests/xgettext-sh-1: Add test cases with ASCII $'...' strings.

gettext-tools/src/x-sh.c
gettext-tools/tests/xgettext-sh-1

index 154b185d756187f0d042d7f42b12edacb932e7e4..c826dcf9b297e83bb399e440b341a9e349b8081c 100644 (file)
@@ -1,5 +1,5 @@
 /* xgettext sh backend.
-   Copyright (C) 2003-2024 Free Software Foundation, Inc.
+   Copyright (C) 2003-2025 Free Software Foundation, Inc.
    Written by Bruno Haible <bruno@clisp.org>, 2003.
 
    This program is free software: you can redistribute it and/or modify
@@ -64,6 +64,9 @@
    - Strings are enclosed in "..."; command substitution, variable
      substitution and arithmetic substitution are performed here as well.
    - '...' is a string without substitutions.
+   - $'...' is a string with escapes but without substitutions.
+     <https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_02_04>
+     <https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html>
    - The list of resulting words is split into commands by semicolon and
      newline.
    - '#' at the beginning of a word introduces a comment until end of line.
@@ -985,7 +988,8 @@ read_word (struct word *wp, int looking_for, flag_region_ty *region)
 
               if (c2 == '\'' && !open_singlequote)
                 {
-                  /* Bash builtin for string with ANSI-C escape sequences.  */
+                  /* $'...': POSIX dollar-single-quoted string.  Also known as
+                     bash builtin for string with ANSI-C escape sequences.  */
                   for (;;)
                     {
                       /* We have to use phase1 throughout this loop,
@@ -1040,6 +1044,41 @@ read_word (struct word *wp, int looking_for, flag_region_ty *region)
                               c = '\v';
                               break;
 
+                            case 'c':
+                              c = phase1_getc ();
+                              if (c >= 'A' && c <= 'Z')
+                                c = c - 'A' + 0x01;
+                              else if (c >= 'a' && c <= 'z')
+                                c = c - 'a' + 0x01;
+                              else if (c == '[')
+                                c = 0x1b; /* ESC */
+                              else if (c == '\\')
+                                {
+                                  c = phase1_getc ();
+                                  if (c == '\\')
+                                    c = 0x1c; /* FS */
+                                  else
+                                    {
+                                      phase1_ungetc (c);
+                                      phase1_ungetc ('\\');
+                                      c = 'c';
+                                    }
+                                }
+                              else if (c == ']')
+                                c = 0x1d; /* GS */
+                              else if (c == '^')
+                                c = 0x1e; /* RS */
+                              else if (c == '_')
+                                c = 0x1f; /* US */
+                              else if (c == '?')
+                                c = 0x7f; /* DEL */
+                              else
+                                {
+                                  phase1_ungetc (c);
+                                  c = 'c';
+                                }
+                              break;
+
                             case 'x':
                               c = phase1_getc ();
                               if ((c >= '0' && c <= '9')
@@ -1120,7 +1159,7 @@ read_word (struct word *wp, int looking_for, flag_region_ty *region)
                 }
               else if (c2 == '"' && !open_doublequote)
                 {
-                  /* Bash builtin for internationalized string.  */
+                  /* $"...": Bash builtin for internationalized string.  */
                   lex_pos_ty pos;
                   struct token string;
 
index 8178d7baa50aa92e4ce98cfae4058050c9ae564d..a98e54ae0e1038b0e1823f02b186397a2394cb4f 100755 (executable)
@@ -4,6 +4,8 @@
 # Test of Shell support: escape sequences, string concatenation,
 # strings with embedded expressions.
 
+# Note! This file contains unescaped ASCII control characters. Edit carefully!
+
 cat <<\EOF > xg-sh-1.sh
 # Test escape sequences expansion.
 
@@ -479,6 +481,20 @@ echo "`echo "\`ngettext 'depth_2_11_squote_6_semi \\\\\\;'\`"`"
 echo "`echo "\`ngettext 'depth_2_11_squote_7_semi \\\\\\\;'\`"`"
 echo "`echo "\`ngettext 'depth_2_11_squote_8_semi \\\\\\\\;'\`"`"
 
+# Test dollar-single-quote strings.
+
+gettext $'depth_0_dollar_posix_0_"ab\"cd\'ef\\gh\eij\fkl\nmn\rop\tqr\vst'
+gettext $'depth_0_dollar_posix_1_\cvab\cVcd\c[ef\c\\gh\c]ij\c?kl'
+gettext $'depth_0_dollar_bash_0_\Eab'
+
+echo `gettext $'depth_1_dollar_posix_0_"ab\"cd\'ef\\gh\eij\fkl\nmn\rop\tqr\vst'`
+echo `gettext $'depth_1_dollar_posix_1_\cvab\cVcd\c[ef\c\\gh\c]ij\c?kl'`
+echo `gettext $'depth_1_dollar_bash_0_\Eab'`
+
+echo `echo \`gettext $'depth_2_dollar_posix_0_"ab\"cd\'ef\\gh\eij\fkl\nmn\rop\tqr\vst'\``
+echo `echo \`gettext $'depth_2_dollar_posix_1_\cvab\cVcd\c[ef\c\\gh\c]ij\c?kl'\``
+echo `echo \`gettext $'depth_2_dollar_bash_0_\Eab'\``
+
 # Test string concatenation.
 
 gettext "concat_0_""part2"
@@ -1870,6 +1886,39 @@ msgstr ""
 msgid "depth_2_11_squote_8_semi \\\\;"
 msgstr ""
 
+msgid ""
+"depth_0_dollar_posix_0_\"ab\"cd'ef\\gh\eij\fkl\n"
+"mn\rop\tqr\vst"
+msgstr ""
+
+msgid "depth_0_dollar_posix_1_\16ab\16cd\eef\1cgh\1dij\7fkl"
+msgstr ""
+
+msgid "depth_0_dollar_bash_0_\eab"
+msgstr ""
+
+msgid ""
+"depth_1_dollar_posix_0_\"ab\"cd'ef\\gh\eij\fkl\n"
+"mn\rop\tqr\vst"
+msgstr ""
+
+msgid "depth_1_dollar_posix_1_\16ab\16cd\eef\1cgh\1dij\7fkl"
+msgstr ""
+
+msgid "depth_1_dollar_bash_0_\eab"
+msgstr ""
+
+msgid ""
+"depth_2_dollar_posix_0_\"ab\"cd'ef\\gh\eij\fkl\n"
+"mn\rop\tqr\vst"
+msgstr ""
+
+msgid "depth_2_dollar_posix_1_\16ab\16cd\eef\1cgh\1dij\7fkl"
+msgstr ""
+
+msgid "depth_2_dollar_bash_0_\eab"
+msgstr ""
+
 msgid "concat_0_part2"
 msgstr ""