| 1 | #!/bin/sh |
| 2 | # ===================================== |
| 3 | # POSIX Compliance Quoting Test Suite for fortsh |
| 4 | # ===================================== |
| 5 | # Tests quoting and escaping per IEEE Std 1003.1-2017 |
| 6 | # Section: Shell Command Language - Quoting |
| 7 | |
| 8 | # Colors (POSIX-compliant way) |
| 9 | RED='\033[0;31m' |
| 10 | GREEN='\033[0;32m' |
| 11 | YELLOW='\033[1;33m' |
| 12 | BLUE='\033[0;34m' |
| 13 | NC='\033[0m' |
| 14 | |
| 15 | # Test identification |
| 16 | TEST_PREFIX="[posix-quoting]" |
| 17 | CURRENT_SECTION="" |
| 18 | TEST_NUM=0 |
| 19 | |
| 20 | PASSED=0 |
| 21 | FAILED=0 |
| 22 | SKIPPED=0 |
| 23 | FAILED_TESTS_LIST="" |
| 24 | |
| 25 | # Get script directory (POSIX way) |
| 26 | SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) |
| 27 | FORTSH_BIN="${FORTSH_BIN:-$SCRIPT_DIR/../bin/fortsh}" |
| 28 | |
| 29 | # Check if fortsh exists |
| 30 | if [ ! -x "$FORTSH_BIN" ]; then |
| 31 | printf "${RED}ERROR${NC}: fortsh binary not found at $FORTSH_BIN\n" |
| 32 | printf "Please run 'make' first or set FORTSH_BIN environment variable\n" |
| 33 | exit 1 |
| 34 | fi |
| 35 | |
| 36 | # Test result trackers |
| 37 | pass() { |
| 38 | TEST_NUM=$((TEST_NUM + 1)) |
| 39 | printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1" |
| 40 | PASSED=$((PASSED + 1)) |
| 41 | } |
| 42 | |
| 43 | fail() { |
| 44 | TEST_NUM=$((TEST_NUM + 1)) |
| 45 | TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}" |
| 46 | printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1" |
| 47 | FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n" |
| 48 | if [ -n "$2" ]; then |
| 49 | printf " expected: %s\n" "$2" |
| 50 | fi |
| 51 | if [ -n "$3" ]; then |
| 52 | printf " got: %s\n" "$3" |
| 53 | fi |
| 54 | FAILED=$((FAILED + 1)) |
| 55 | } |
| 56 | |
| 57 | skip() { |
| 58 | TEST_NUM=$((TEST_NUM + 1)) |
| 59 | printf "${YELLOW}⊘ SKIP${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s - %s\n" "$1" "$2" |
| 60 | SKIPPED=$((SKIPPED + 1)) |
| 61 | } |
| 62 | |
| 63 | section() { |
| 64 | CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0") |
| 65 | TEST_NUM=0 |
| 66 | printf "\n" |
| 67 | printf "${BLUE}==========================================\n" |
| 68 | printf "%s\n" "$1" |
| 69 | printf "==========================================${NC}\n" |
| 70 | } |
| 71 | |
| 72 | # ===================================== |
| 73 | section "446. SINGLE QUOTES" |
| 74 | # ===================================== |
| 75 | |
| 76 | result=$("$FORTSH_BIN" -c "echo 'hello world'" 2>&1) |
| 77 | if [ "$result" = "hello world" ]; then |
| 78 | pass "Single quotes preserve spaces" |
| 79 | else |
| 80 | fail "Single quotes preserve spaces" "hello world" "$result" |
| 81 | fi |
| 82 | |
| 83 | result=$("$FORTSH_BIN" -c "x=test; echo '\$x'" 2>&1) |
| 84 | if [ "$result" = '$x' ]; then |
| 85 | pass "Single quotes prevent variable expansion" |
| 86 | else |
| 87 | fail "Single quotes prevent variable expansion" '$x' "$result" |
| 88 | fi |
| 89 | |
| 90 | result=$("$FORTSH_BIN" -c "echo 'back\\slash'" 2>&1) |
| 91 | if [ "$result" = 'back\slash' ]; then |
| 92 | pass "Single quotes preserve backslash" |
| 93 | else |
| 94 | fail "Single quotes preserve backslash" 'back\slash' "$result" |
| 95 | fi |
| 96 | |
| 97 | result=$("$FORTSH_BIN" -c "echo 'has \"double\" quotes'" 2>&1) |
| 98 | if [ "$result" = 'has "double" quotes' ]; then |
| 99 | pass "Single quotes preserve double quotes" |
| 100 | else |
| 101 | fail "Single quotes preserve double quotes" 'has "double" quotes' "$result" |
| 102 | fi |
| 103 | |
| 104 | result=$("$FORTSH_BIN" -c "echo '\$(echo no)'" 2>&1) |
| 105 | if [ "$result" = '$(echo no)' ]; then |
| 106 | pass "Single quotes prevent command substitution" |
| 107 | else |
| 108 | fail "Single quotes prevent command substitution" '$(echo no)' "$result" |
| 109 | fi |
| 110 | |
| 111 | # ===================================== |
| 112 | section "447. DOUBLE QUOTES" |
| 113 | # ===================================== |
| 114 | |
| 115 | result=$("$FORTSH_BIN" -c 'echo "hello world"' 2>&1) |
| 116 | if [ "$result" = "hello world" ]; then |
| 117 | pass "Double quotes preserve spaces" |
| 118 | else |
| 119 | fail "Double quotes preserve spaces" "hello world" "$result" |
| 120 | fi |
| 121 | |
| 122 | result=$("$FORTSH_BIN" -c 'x=test; echo "$x"' 2>&1) |
| 123 | if [ "$result" = "test" ]; then |
| 124 | pass "Double quotes allow variable expansion" |
| 125 | else |
| 126 | fail "Double quotes allow variable expansion" "test" "$result" |
| 127 | fi |
| 128 | |
| 129 | result=$("$FORTSH_BIN" -c 'echo "$(echo hello)"' 2>&1) |
| 130 | if [ "$result" = "hello" ]; then |
| 131 | pass "Double quotes allow command substitution" |
| 132 | else |
| 133 | fail "Double quotes allow command substitution" "hello" "$result" |
| 134 | fi |
| 135 | |
| 136 | result=$("$FORTSH_BIN" -c 'echo "has '\''single'\'' quotes"' 2>&1) |
| 137 | if [ "$result" = "has 'single' quotes" ]; then |
| 138 | pass "Double quotes preserve single quotes" |
| 139 | else |
| 140 | fail "Double quotes preserve single quotes" "has 'single' quotes" "$result" |
| 141 | fi |
| 142 | |
| 143 | result=$("$FORTSH_BIN" -c 'echo "escaped \"quote\""' 2>&1) |
| 144 | if [ "$result" = 'escaped "quote"' ]; then |
| 145 | pass "Double quotes with escaped quotes" |
| 146 | else |
| 147 | fail "Double quotes with escaped quotes" 'escaped "quote"' "$result" |
| 148 | fi |
| 149 | |
| 150 | result=$("$FORTSH_BIN" -c 'echo "back\\slash"' 2>&1) |
| 151 | if [ "$result" = 'back\slash' ]; then |
| 152 | pass "Double quotes with escaped backslash" |
| 153 | else |
| 154 | fail "Double quotes with escaped backslash" 'back\slash' "$result" |
| 155 | fi |
| 156 | |
| 157 | result=$("$FORTSH_BIN" -c 'echo "dollar\$sign"' 2>&1) |
| 158 | if [ "$result" = 'dollar$sign' ]; then |
| 159 | pass "Double quotes with escaped dollar" |
| 160 | else |
| 161 | fail "Double quotes with escaped dollar" 'dollar$sign' "$result" |
| 162 | fi |
| 163 | |
| 164 | # ===================================== |
| 165 | section "448. BACKSLASH ESCAPING" |
| 166 | # ===================================== |
| 167 | |
| 168 | result=$("$FORTSH_BIN" -c 'echo hello\ world' 2>&1) |
| 169 | if [ "$result" = "hello world" ]; then |
| 170 | pass "Backslash escapes space" |
| 171 | else |
| 172 | fail "Backslash escapes space" "hello world" "$result" |
| 173 | fi |
| 174 | |
| 175 | result=$("$FORTSH_BIN" -c 'x=test; echo \$x' 2>&1) |
| 176 | if [ "$result" = '$x' ]; then |
| 177 | pass "Backslash escapes dollar sign" |
| 178 | else |
| 179 | fail "Backslash escapes dollar sign" '$x' "$result" |
| 180 | fi |
| 181 | |
| 182 | result=$("$FORTSH_BIN" -c 'echo back\\slash' 2>&1) |
| 183 | if [ "$result" = 'back\slash' ]; then |
| 184 | pass "Backslash escapes backslash" |
| 185 | else |
| 186 | fail "Backslash escapes backslash" 'back\slash' "$result" |
| 187 | fi |
| 188 | |
| 189 | result=$("$FORTSH_BIN" -c 'echo hello\ |
| 190 | world' 2>&1) |
| 191 | if [ "$result" = "helloworld" ]; then |
| 192 | pass "Backslash-newline line continuation" |
| 193 | else |
| 194 | fail "Backslash-newline line continuation" "helloworld" "$result" |
| 195 | fi |
| 196 | |
| 197 | # ===================================== |
| 198 | section "449. MIXED QUOTING" |
| 199 | # ===================================== |
| 200 | |
| 201 | result=$("$FORTSH_BIN" -c 'echo "hello"'\''world'\''' 2>&1) |
| 202 | # Adjacent quoted strings concatenate: "hello" + 'world' = helloworld |
| 203 | if [ "$result" = "helloworld" ]; then |
| 204 | pass "Adjacent double and single quotes" |
| 205 | else |
| 206 | fail "Adjacent double and single quotes" "helloworld" "$result" |
| 207 | fi |
| 208 | |
| 209 | result=$("$FORTSH_BIN" -c 'x=val; echo "$x"'\''$x'\''' 2>&1) |
| 210 | if [ "$result" = 'val$x' ]; then |
| 211 | pass "Mixed expansion and literal" |
| 212 | else |
| 213 | fail "Mixed expansion and literal" 'val$x' "$result" |
| 214 | fi |
| 215 | |
| 216 | result=$("$FORTSH_BIN" -c "echo 'don'\\''t'" 2>&1) |
| 217 | if [ "$result" = "don't" ]; then |
| 218 | pass "Single quote in single quoted string" |
| 219 | else |
| 220 | fail "Single quote in single quoted string" "don't" "$result" |
| 221 | fi |
| 222 | |
| 223 | # ===================================== |
| 224 | section "450. QUOTING SPECIAL CHARACTERS" |
| 225 | # ===================================== |
| 226 | |
| 227 | result=$("$FORTSH_BIN" -c 'echo "semi;colon"' 2>&1) |
| 228 | if [ "$result" = "semi;colon" ]; then |
| 229 | pass "Semicolon in double quotes" |
| 230 | else |
| 231 | fail "Semicolon in double quotes" "semi;colon" "$result" |
| 232 | fi |
| 233 | |
| 234 | result=$("$FORTSH_BIN" -c "echo 'pipe|char'" 2>&1) |
| 235 | if [ "$result" = "pipe|char" ]; then |
| 236 | pass "Pipe in single quotes" |
| 237 | else |
| 238 | fail "Pipe in single quotes" "pipe|char" "$result" |
| 239 | fi |
| 240 | |
| 241 | result=$("$FORTSH_BIN" -c 'echo "ampersand&here"' 2>&1) |
| 242 | if [ "$result" = "ampersand&here" ]; then |
| 243 | pass "Ampersand in double quotes" |
| 244 | else |
| 245 | fail "Ampersand in double quotes" "ampersand&here" "$result" |
| 246 | fi |
| 247 | |
| 248 | result=$("$FORTSH_BIN" -c "echo 'less<greater>'" 2>&1) |
| 249 | if [ "$result" = "less<greater>" ]; then |
| 250 | pass "Angle brackets in single quotes" |
| 251 | else |
| 252 | fail "Angle brackets in single quotes" "less<greater>" "$result" |
| 253 | fi |
| 254 | |
| 255 | result=$("$FORTSH_BIN" -c 'echo "paren(here)"' 2>&1) |
| 256 | if [ "$result" = "paren(here)" ]; then |
| 257 | pass "Parentheses in double quotes" |
| 258 | else |
| 259 | fail "Parentheses in double quotes" "paren(here)" "$result" |
| 260 | fi |
| 261 | |
| 262 | result=$("$FORTSH_BIN" -c "echo 'star*glob?'" 2>&1) |
| 263 | if [ "$result" = "star*glob?" ]; then |
| 264 | pass "Glob chars in single quotes" |
| 265 | else |
| 266 | fail "Glob chars in single quotes" "star*glob?" "$result" |
| 267 | fi |
| 268 | |
| 269 | result=$("$FORTSH_BIN" -c 'echo "hash#comment"' 2>&1) |
| 270 | if [ "$result" = "hash#comment" ]; then |
| 271 | pass "Hash in double quotes" |
| 272 | else |
| 273 | fail "Hash in double quotes" "hash#comment" "$result" |
| 274 | fi |
| 275 | |
| 276 | # ===================================== |
| 277 | section "451. EMPTY STRINGS" |
| 278 | # ===================================== |
| 279 | |
| 280 | result=$("$FORTSH_BIN" -c 'echo ""' 2>&1) |
| 281 | if [ "$result" = "" ]; then |
| 282 | pass "Empty double-quoted string" |
| 283 | else |
| 284 | fail "Empty double-quoted string" "(empty)" "$result" |
| 285 | fi |
| 286 | |
| 287 | result=$("$FORTSH_BIN" -c "echo ''" 2>&1) |
| 288 | if [ "$result" = "" ]; then |
| 289 | pass "Empty single-quoted string" |
| 290 | else |
| 291 | fail "Empty single-quoted string" "(empty)" "$result" |
| 292 | fi |
| 293 | |
| 294 | result=$("$FORTSH_BIN" -c 'x=""; echo "[$x]"' 2>&1) |
| 295 | if [ "$result" = "[]" ]; then |
| 296 | pass "Empty variable in quotes" |
| 297 | else |
| 298 | fail "Empty variable in quotes" "[]" "$result" |
| 299 | fi |
| 300 | |
| 301 | result=$("$FORTSH_BIN" -c 'echo a "" b' 2>&1) |
| 302 | if [ "$result" = "a b" ]; then |
| 303 | pass "Empty string preserves word" |
| 304 | else |
| 305 | fail "Empty string preserves word" "a b" "$result" |
| 306 | fi |
| 307 | |
| 308 | # ===================================== |
| 309 | section "452. WORD SPLITTING" |
| 310 | # ===================================== |
| 311 | |
| 312 | result=$("$FORTSH_BIN" -c 'x="a b c"; for w in $x; do echo "[$w]"; done' 2>&1) |
| 313 | expected=$(printf "[a]\n[b]\n[c]") |
| 314 | if [ "$result" = "$expected" ]; then |
| 315 | pass "Unquoted variable splits on whitespace" |
| 316 | else |
| 317 | fail "Unquoted variable splits on whitespace" "$expected" "$result" |
| 318 | fi |
| 319 | |
| 320 | result=$("$FORTSH_BIN" -c 'x="a b c"; for w in "$x"; do echo "[$w]"; done' 2>&1) |
| 321 | if [ "$result" = "[a b c]" ]; then |
| 322 | pass "Quoted variable prevents splitting" |
| 323 | else |
| 324 | fail "Quoted variable prevents splitting" "[a b c]" "$result" |
| 325 | fi |
| 326 | |
| 327 | result=$("$FORTSH_BIN" -c 'x="a b"; echo $x' 2>&1) |
| 328 | if [ "$result" = "a b" ]; then |
| 329 | pass "Word splitting collapses spaces" |
| 330 | else |
| 331 | fail "Word splitting collapses spaces" "a b" "$result" |
| 332 | fi |
| 333 | |
| 334 | result=$("$FORTSH_BIN" -c 'x="a b"; echo "$x"' 2>&1) |
| 335 | if [ "$result" = "a b" ]; then |
| 336 | pass "Quoting preserves multiple spaces" |
| 337 | else |
| 338 | fail "Quoting preserves multiple spaces" "a b" "$result" |
| 339 | fi |
| 340 | |
| 341 | # ===================================== |
| 342 | section "453. GLOB PREVENTION" |
| 343 | # ===================================== |
| 344 | |
| 345 | result=$("$FORTSH_BIN" -c 'echo "*"' 2>&1) |
| 346 | if [ "$result" = "*" ]; then |
| 347 | pass "Double quotes prevent glob expansion" |
| 348 | else |
| 349 | fail "Double quotes prevent glob expansion" "*" "$result" |
| 350 | fi |
| 351 | |
| 352 | result=$("$FORTSH_BIN" -c "echo '[a-z]*'" 2>&1) |
| 353 | if [ "$result" = "[a-z]*" ]; then |
| 354 | pass "Single quotes prevent bracket expansion" |
| 355 | else |
| 356 | fail "Single quotes prevent bracket expansion" "[a-z]*" "$result" |
| 357 | fi |
| 358 | |
| 359 | result=$("$FORTSH_BIN" -c 'echo "?"' 2>&1) |
| 360 | if [ "$result" = "?" ]; then |
| 361 | pass "Double quotes prevent ? expansion" |
| 362 | else |
| 363 | fail "Double quotes prevent ? expansion" "?" "$result" |
| 364 | fi |
| 365 | |
| 366 | # ===================================== |
| 367 | section "454. QUOTE IN VARIABLE" |
| 368 | # ===================================== |
| 369 | |
| 370 | result=$("$FORTSH_BIN" -c "x=\"has 'quotes'\"; echo \"\$x\"" 2>&1) |
| 371 | if [ "$result" = "has 'quotes'" ]; then |
| 372 | pass "Single quotes inside double-quoted variable" |
| 373 | else |
| 374 | fail "Single quotes inside double-quoted variable" "has 'quotes'" "$result" |
| 375 | fi |
| 376 | |
| 377 | result=$("$FORTSH_BIN" -c 'x="has \"quotes\""; echo "$x"' 2>&1) |
| 378 | if [ "$result" = 'has "quotes"' ]; then |
| 379 | pass "Double quotes inside variable" |
| 380 | else |
| 381 | fail "Double quotes inside variable" 'has "quotes"' "$result" |
| 382 | fi |
| 383 | |
| 384 | # ===================================== |
| 385 | section "455. QUOTING IN ASSIGNMENTS" |
| 386 | # ===================================== |
| 387 | |
| 388 | result=$("$FORTSH_BIN" -c 'x="hello world"; echo $x' 2>&1) |
| 389 | if [ "$result" = "hello world" ]; then |
| 390 | pass "Quoted assignment with spaces" |
| 391 | else |
| 392 | fail "Quoted assignment with spaces" "hello world" "$result" |
| 393 | fi |
| 394 | |
| 395 | result=$("$FORTSH_BIN" -c "x='no \$expansion'; echo \"\$x\"" 2>&1) |
| 396 | if [ "$result" = 'no $expansion' ]; then |
| 397 | pass "Single-quoted assignment" |
| 398 | else |
| 399 | fail "Single-quoted assignment" 'no $expansion' "$result" |
| 400 | fi |
| 401 | |
| 402 | result=$("$FORTSH_BIN" -c 'y=value; x="with $y"; echo "$x"' 2>&1) |
| 403 | if [ "$result" = "with value" ]; then |
| 404 | pass "Variable expansion in assignment" |
| 405 | else |
| 406 | fail "Variable expansion in assignment" "with value" "$result" |
| 407 | fi |
| 408 | |
| 409 | # ===================================== |
| 410 | section "456. QUOTING IN COMMAND SUBSTITUTION" |
| 411 | # ===================================== |
| 412 | |
| 413 | result=$("$FORTSH_BIN" -c 'x=$(echo "hello world"); echo "$x"' 2>&1) |
| 414 | if [ "$result" = "hello world" ]; then |
| 415 | pass "Quotes inside command substitution" |
| 416 | else |
| 417 | fail "Quotes inside command substitution" "hello world" "$result" |
| 418 | fi |
| 419 | |
| 420 | result=$("$FORTSH_BIN" -c 'x="$(echo "nested quotes")"; echo "$x"' 2>&1) |
| 421 | if [ "$result" = "nested quotes" ]; then |
| 422 | pass "Nested quotes in command substitution" |
| 423 | else |
| 424 | fail "Nested quotes in command substitution" "nested quotes" "$result" |
| 425 | fi |
| 426 | |
| 427 | # ===================================== |
| 428 | section "457. BACKSLASH IN DOUBLE QUOTES" |
| 429 | # ===================================== |
| 430 | |
| 431 | # POSIX: In double quotes, \\\\ (4 backslashes) becomes \\ (2 backslashes) |
| 432 | # Each pair of \\ in double quotes produces one literal backslash |
| 433 | result=$("$FORTSH_BIN" -c 'echo "back\\\\slash"' 2>&1) |
| 434 | if [ "$result" = 'back\\slash' ]; then |
| 435 | pass "Backslash-backslash in double quotes" |
| 436 | else |
| 437 | fail "Backslash-backslash in double quotes" 'back\\slash' "$result" |
| 438 | fi |
| 439 | |
| 440 | result=$("$FORTSH_BIN" -c 'echo "newline\\n"' 2>&1) |
| 441 | if [ "$result" = 'newline\n' ]; then |
| 442 | pass "Backslash-n in double quotes (literal)" |
| 443 | else |
| 444 | fail "Backslash-n in double quotes (literal)" 'newline\n' "$result" |
| 445 | fi |
| 446 | |
| 447 | result=$("$FORTSH_BIN" -c 'echo "tab\\t"' 2>&1) |
| 448 | if [ "$result" = 'tab\t' ]; then |
| 449 | pass "Backslash-t in double quotes (literal)" |
| 450 | else |
| 451 | fail "Backslash-t in double quotes (literal)" 'tab\t' "$result" |
| 452 | fi |
| 453 | |
| 454 | # ===================================== |
| 455 | section "458. DOLLAR IN QUOTES" |
| 456 | # ===================================== |
| 457 | |
| 458 | result=$("$FORTSH_BIN" -c 'echo "cost: \$5"' 2>&1) |
| 459 | if [ "$result" = 'cost: $5' ]; then |
| 460 | pass "Escaped dollar in double quotes" |
| 461 | else |
| 462 | fail "Escaped dollar in double quotes" 'cost: $5' "$result" |
| 463 | fi |
| 464 | |
| 465 | result=$("$FORTSH_BIN" -c "echo 'cost: \$5'" 2>&1) |
| 466 | if [ "$result" = 'cost: $5' ]; then |
| 467 | pass "Dollar in single quotes (literal)" |
| 468 | else |
| 469 | fail "Dollar in single quotes (literal)" 'cost: $5' "$result" |
| 470 | fi |
| 471 | |
| 472 | # ===================================== |
| 473 | section "459. NEWLINE IN QUOTES" |
| 474 | # ===================================== |
| 475 | |
| 476 | result=$("$FORTSH_BIN" -c 'echo "line1 |
| 477 | line2"' 2>&1) |
| 478 | expected=$(printf "line1\nline2") |
| 479 | if [ "$result" = "$expected" ]; then |
| 480 | pass "Literal newline in double quotes" |
| 481 | else |
| 482 | fail "Literal newline in double quotes" "$expected" "$result" |
| 483 | fi |
| 484 | |
| 485 | result=$("$FORTSH_BIN" -c "echo 'line1 |
| 486 | line2'" 2>&1) |
| 487 | expected=$(printf "line1\nline2") |
| 488 | if [ "$result" = "$expected" ]; then |
| 489 | pass "Literal newline in single quotes" |
| 490 | else |
| 491 | fail "Literal newline in single quotes" "$expected" "$result" |
| 492 | fi |
| 493 | |
| 494 | # ===================================== |
| 495 | section "460. TAB AND SPACE IN QUOTES" |
| 496 | # ===================================== |
| 497 | |
| 498 | result=$("$FORTSH_BIN" -c 'echo " tab"' 2>&1) |
| 499 | if printf "%s" "$result" | grep -q " "; then |
| 500 | pass "Literal tab in double quotes" |
| 501 | else |
| 502 | fail "Literal tab in double quotes" "string with tab" "$result" |
| 503 | fi |
| 504 | |
| 505 | result=$("$FORTSH_BIN" -c 'echo " spaces "' 2>&1) |
| 506 | if [ "$result" = " spaces " ]; then |
| 507 | pass "Multiple spaces in double quotes" |
| 508 | else |
| 509 | fail "Multiple spaces in double quotes" " spaces " "$result" |
| 510 | fi |
| 511 | |
| 512 | # ===================================== |
| 513 | section "461. QUOTING IN FOR LOOP" |
| 514 | # ===================================== |
| 515 | |
| 516 | result=$("$FORTSH_BIN" -c 'for x in "a b" c; do echo "[$x]"; done' 2>&1) |
| 517 | expected=$(printf "[a b]\n[c]") |
| 518 | if [ "$result" = "$expected" ]; then |
| 519 | pass "Quoted string in for list" |
| 520 | else |
| 521 | fail "Quoted string in for list" "$expected" "$result" |
| 522 | fi |
| 523 | |
| 524 | result=$("$FORTSH_BIN" -c 'list="a b c"; for x in $list; do echo "[$x]"; done' 2>&1) |
| 525 | expected=$(printf "[a]\n[b]\n[c]") |
| 526 | if [ "$result" = "$expected" ]; then |
| 527 | pass "Unquoted var splits in for" |
| 528 | else |
| 529 | fail "Unquoted var splits in for" "$expected" "$result" |
| 530 | fi |
| 531 | |
| 532 | result=$("$FORTSH_BIN" -c 'list="a b c"; for x in "$list"; do echo "[$x]"; done' 2>&1) |
| 533 | if [ "$result" = "[a b c]" ]; then |
| 534 | pass "Quoted var no split in for" |
| 535 | else |
| 536 | fail "Quoted var no split in for" "[a b c]" "$result" |
| 537 | fi |
| 538 | |
| 539 | # ===================================== |
| 540 | section "462. QUOTING IN CASE STATEMENT" |
| 541 | # ===================================== |
| 542 | |
| 543 | result=$("$FORTSH_BIN" -c 'x="hello world"; case "$x" in "hello world") echo match;; esac' 2>&1) |
| 544 | if [ "$result" = "match" ]; then |
| 545 | pass "Quoted string in case word" |
| 546 | else |
| 547 | fail "Quoted string in case word" "match" "$result" |
| 548 | fi |
| 549 | |
| 550 | result=$("$FORTSH_BIN" -c 'case "a b" in "a b") echo yes;; *) echo no;; esac' 2>&1) |
| 551 | if [ "$result" = "yes" ]; then |
| 552 | pass "Quoted pattern in case" |
| 553 | else |
| 554 | fail "Quoted pattern in case" "yes" "$result" |
| 555 | fi |
| 556 | |
| 557 | # ===================================== |
| 558 | section "463. QUOTING SPECIAL SHELL CHARS" |
| 559 | # ===================================== |
| 560 | |
| 561 | result=$("$FORTSH_BIN" -c 'echo "hello; world"' 2>&1) |
| 562 | if [ "$result" = "hello; world" ]; then |
| 563 | pass "Semicolon in double quotes" |
| 564 | else |
| 565 | fail "Semicolon in double quotes" "hello; world" "$result" |
| 566 | fi |
| 567 | |
| 568 | result=$("$FORTSH_BIN" -c "echo 'a && b'" 2>&1) |
| 569 | if [ "$result" = "a && b" ]; then |
| 570 | pass "Double ampersand in single quotes" |
| 571 | else |
| 572 | fail "Double ampersand in single quotes" "a && b" "$result" |
| 573 | fi |
| 574 | |
| 575 | result=$("$FORTSH_BIN" -c 'echo "a || b"' 2>&1) |
| 576 | if [ "$result" = "a || b" ]; then |
| 577 | pass "Double pipe in double quotes" |
| 578 | else |
| 579 | fail "Double pipe in double quotes" "a || b" "$result" |
| 580 | fi |
| 581 | |
| 582 | result=$("$FORTSH_BIN" -c "echo 'back\`tick'" 2>&1) |
| 583 | if [ "$result" = 'back`tick' ]; then |
| 584 | pass "Backtick in single quotes" |
| 585 | else |
| 586 | fail "Backtick in single quotes" 'back`tick' "$result" |
| 587 | fi |
| 588 | |
| 589 | # ===================================== |
| 590 | section "464. QUOTING IN ARITHMETIC" |
| 591 | # ===================================== |
| 592 | |
| 593 | result=$("$FORTSH_BIN" -c 'x=5; echo $((x + 3))' 2>&1) |
| 594 | if [ "$result" = "8" ]; then |
| 595 | pass "Unquoted var in arithmetic" |
| 596 | else |
| 597 | fail "Unquoted var in arithmetic" "8" "$result" |
| 598 | fi |
| 599 | |
| 600 | result=$("$FORTSH_BIN" -c 'x="5"; echo $((x + 3))' 2>&1) |
| 601 | if [ "$result" = "8" ]; then |
| 602 | pass "Quoted assignment used in arithmetic" |
| 603 | else |
| 604 | fail "Quoted assignment used in arithmetic" "8" "$result" |
| 605 | fi |
| 606 | |
| 607 | # ===================================== |
| 608 | section "465. ESCAPE SEQUENCES" |
| 609 | # ===================================== |
| 610 | |
| 611 | result=$("$FORTSH_BIN" -c 'echo "hello\tworld"' 2>&1) |
| 612 | if echo "$result" | grep -q "hello"; then |
| 613 | pass "Backslash-t in double quotes" |
| 614 | else |
| 615 | fail "Backslash-t in double quotes" |
| 616 | fi |
| 617 | |
| 618 | # Test backslash-n literal in single quotes (use double backslash for POSIX sh compatibility) |
| 619 | result=$("$FORTSH_BIN" -c "echo 'hello\\nworld'" 2>&1) |
| 620 | if printf '%s' "$result" | grep -q 'hello.nworld'; then |
| 621 | pass "Backslash-n literal in single quotes" |
| 622 | else |
| 623 | fail "Backslash-n literal in single quotes" |
| 624 | fi |
| 625 | |
| 626 | # ===================================== |
| 627 | section "466. QUOTING AND WORD SPLITTING" |
| 628 | # ===================================== |
| 629 | |
| 630 | result=$("$FORTSH_BIN" -c 'x="a b c"; set -- $x; echo $#' 2>&1) |
| 631 | if [ "$result" = "3" ]; then |
| 632 | pass "Unquoted var splits on spaces" |
| 633 | else |
| 634 | fail "Unquoted var splits on spaces" "3" "$result" |
| 635 | fi |
| 636 | |
| 637 | result=$("$FORTSH_BIN" -c 'x="a b c"; set -- "$x"; echo $#' 2>&1) |
| 638 | if [ "$result" = "1" ]; then |
| 639 | pass "Quoted var prevents splitting" |
| 640 | else |
| 641 | fail "Quoted var prevents splitting" "1" "$result" |
| 642 | fi |
| 643 | |
| 644 | result=$("$FORTSH_BIN" -c 'x=""; set -- $x; echo $#' 2>&1) |
| 645 | if [ "$result" = "0" ]; then |
| 646 | pass "Empty unquoted var produces no args" |
| 647 | else |
| 648 | fail "Empty unquoted var produces no args" "0" "$result" |
| 649 | fi |
| 650 | |
| 651 | result=$("$FORTSH_BIN" -c 'x=""; set -- "$x"; echo $#' 2>&1) |
| 652 | if [ "$result" = "1" ]; then |
| 653 | pass "Empty quoted var produces one empty arg" |
| 654 | else |
| 655 | fail "Empty quoted var produces one empty arg" "1" "$result" |
| 656 | fi |
| 657 | |
| 658 | # ===================================== |
| 659 | section "467. QUOTING SPECIAL PARAMETERS" |
| 660 | # ===================================== |
| 661 | |
| 662 | result=$("$FORTSH_BIN" -c 'set -- a b c; echo "$@" | wc -w' 2>&1) |
| 663 | if [ "$result" -eq 3 ] 2>/dev/null; then |
| 664 | pass 'Quoted $@ preserves args' |
| 665 | else |
| 666 | fail 'Quoted $@ preserves args' "3" "$result" |
| 667 | fi |
| 668 | |
| 669 | result=$("$FORTSH_BIN" -c 'set -- a b c; echo "$*"' 2>&1) |
| 670 | if [ "$result" = "a b c" ]; then |
| 671 | pass 'Quoted $* joins args' |
| 672 | else |
| 673 | fail 'Quoted $* joins args' "a b c" "$result" |
| 674 | fi |
| 675 | |
| 676 | result=$("$FORTSH_BIN" -c 'set -- a "b c" d; for x in "$@"; do echo "[$x]"; done' 2>&1) |
| 677 | expected=$(printf "[a]\n[b c]\n[d]") |
| 678 | if [ "$result" = "$expected" ]; then |
| 679 | pass 'Quoted $@ preserves quoting in original args' |
| 680 | else |
| 681 | fail 'Quoted $@ preserves quoting in original args' |
| 682 | fi |
| 683 | |
| 684 | # ===================================== |
| 685 | section "468. NESTED QUOTING" |
| 686 | # ===================================== |
| 687 | |
| 688 | result=$("$FORTSH_BIN" -c "echo 'single \"with\" double'" 2>&1) |
| 689 | if [ "$result" = 'single "with" double' ]; then |
| 690 | pass "Double quotes inside single quotes" |
| 691 | else |
| 692 | fail "Double quotes inside single quotes" |
| 693 | fi |
| 694 | |
| 695 | result=$("$FORTSH_BIN" -c 'echo "double '\''with'\'' single"' 2>&1) |
| 696 | if [ "$result" = "double 'with' single" ]; then |
| 697 | pass "Single quotes inside double quotes (escaped)" |
| 698 | else |
| 699 | fail "Single quotes inside double quotes (escaped)" |
| 700 | fi |
| 701 | |
| 702 | # ===================================== |
| 703 | section "469. QUOTING IN ASSIGNMENTS" |
| 704 | # ===================================== |
| 705 | |
| 706 | result=$("$FORTSH_BIN" -c 'x="hello world"; echo $x' 2>&1) |
| 707 | if [ "$result" = "hello world" ]; then |
| 708 | pass "Quoted assignment with spaces" |
| 709 | else |
| 710 | fail "Quoted assignment with spaces" "hello world" "$result" |
| 711 | fi |
| 712 | |
| 713 | result=$("$FORTSH_BIN" -c "x='hello world'; echo \$x" 2>&1) |
| 714 | if [ "$result" = "hello world" ]; then |
| 715 | pass "Single-quoted assignment with spaces" |
| 716 | else |
| 717 | fail "Single-quoted assignment with spaces" "hello world" "$result" |
| 718 | fi |
| 719 | |
| 720 | result=$("$FORTSH_BIN" -c 'x=hello\ world; echo $x' 2>&1) |
| 721 | if [ "$result" = "hello world" ]; then |
| 722 | pass "Escaped space in assignment" |
| 723 | else |
| 724 | fail "Escaped space in assignment" "hello world" "$result" |
| 725 | fi |
| 726 | |
| 727 | # ===================================== |
| 728 | section "470. QUOTING IN COMMAND SUBSTITUTION" |
| 729 | # ===================================== |
| 730 | |
| 731 | result=$("$FORTSH_BIN" -c 'x=$(echo "hello world"); echo "$x"' 2>&1) |
| 732 | if [ "$result" = "hello world" ]; then |
| 733 | pass "Quoted string in command substitution" |
| 734 | else |
| 735 | fail "Quoted string in command substitution" "hello world" "$result" |
| 736 | fi |
| 737 | |
| 738 | result=$("$FORTSH_BIN" -c 'x=`echo "hello world"`; echo "$x"' 2>&1) |
| 739 | if [ "$result" = "hello world" ]; then |
| 740 | pass "Quoted string in backtick substitution" |
| 741 | else |
| 742 | fail "Quoted string in backtick substitution" "hello world" "$result" |
| 743 | fi |
| 744 | |
| 745 | # ===================================== |
| 746 | section "471. QUOTING AND GLOB PREVENTION" |
| 747 | # ===================================== |
| 748 | |
| 749 | result=$("$FORTSH_BIN" -c 'echo "*"' 2>&1) |
| 750 | if [ "$result" = "*" ]; then |
| 751 | pass "Quoted asterisk is literal" |
| 752 | else |
| 753 | fail "Quoted asterisk is literal" "*" "$result" |
| 754 | fi |
| 755 | |
| 756 | result=$("$FORTSH_BIN" -c 'echo "?"' 2>&1) |
| 757 | if [ "$result" = "?" ]; then |
| 758 | pass "Quoted question mark is literal" |
| 759 | else |
| 760 | fail "Quoted question mark is literal" "?" "$result" |
| 761 | fi |
| 762 | |
| 763 | result=$("$FORTSH_BIN" -c 'echo "[abc]"' 2>&1) |
| 764 | if [ "$result" = "[abc]" ]; then |
| 765 | pass "Quoted brackets are literal" |
| 766 | else |
| 767 | fail "Quoted brackets are literal" "[abc]" "$result" |
| 768 | fi |
| 769 | |
| 770 | # ===================================== |
| 771 | section "472. QUOTING IN TESTS" |
| 772 | # ===================================== |
| 773 | |
| 774 | result=$("$FORTSH_BIN" -c 'x=""; [ -z "$x" ] && echo empty' 2>&1) |
| 775 | if [ "$result" = "empty" ]; then |
| 776 | pass "Quoted empty var in test -z" |
| 777 | else |
| 778 | fail "Quoted empty var in test -z" "empty" "$result" |
| 779 | fi |
| 780 | |
| 781 | result=$("$FORTSH_BIN" -c 'x="hello"; [ -n "$x" ] && echo nonempty' 2>&1) |
| 782 | if [ "$result" = "nonempty" ]; then |
| 783 | pass "Quoted var in test -n" |
| 784 | else |
| 785 | fail "Quoted var in test -n" "nonempty" "$result" |
| 786 | fi |
| 787 | |
| 788 | result=$("$FORTSH_BIN" -c 'x="a b"; [ "$x" = "a b" ] && echo match' 2>&1) |
| 789 | if [ "$result" = "match" ]; then |
| 790 | pass "Quoted var with spaces in test =" |
| 791 | else |
| 792 | fail "Quoted var with spaces in test =" "match" "$result" |
| 793 | fi |
| 794 | |
| 795 | # ===================================== |
| 796 | section "473. QUOTING IN CASE PATTERNS" |
| 797 | # ===================================== |
| 798 | |
| 799 | result=$("$FORTSH_BIN" -c 'x="*"; case "$x" in "*") echo literal;; esac' 2>&1) |
| 800 | if [ "$result" = "literal" ]; then |
| 801 | pass "Quoted asterisk matches literally in case" |
| 802 | else |
| 803 | fail "Quoted asterisk matches literally in case" "literal" "$result" |
| 804 | fi |
| 805 | |
| 806 | result=$("$FORTSH_BIN" -c 'x="a b"; case "$x" in "a b") echo match;; esac' 2>&1) |
| 807 | if [ "$result" = "match" ]; then |
| 808 | pass "Quoted pattern with space in case" |
| 809 | else |
| 810 | fail "Quoted pattern with space in case" "match" "$result" |
| 811 | fi |
| 812 | |
| 813 | # ===================================== |
| 814 | section "474. QUOTING IN FUNCTION ARGS" |
| 815 | # ===================================== |
| 816 | |
| 817 | result=$("$FORTSH_BIN" -c 'f() { echo "[$1]"; }; f "hello world"' 2>&1) |
| 818 | if [ "$result" = "[hello world]" ]; then |
| 819 | pass "Quoted arg to function" |
| 820 | else |
| 821 | fail "Quoted arg to function" "[hello world]" "$result" |
| 822 | fi |
| 823 | |
| 824 | result=$("$FORTSH_BIN" -c 'f() { echo $#; }; f "a b" "c d"' 2>&1) |
| 825 | if [ "$result" = "2" ]; then |
| 826 | pass "Quoted args count in function" |
| 827 | else |
| 828 | fail "Quoted args count in function" "2" "$result" |
| 829 | fi |
| 830 | |
| 831 | # ===================================== |
| 832 | section "475. DOLLAR IN QUOTES" |
| 833 | # ===================================== |
| 834 | |
| 835 | result=$("$FORTSH_BIN" -c 'echo "\$HOME"' 2>&1) |
| 836 | if [ "$result" = '$HOME' ]; then |
| 837 | pass "Escaped dollar in double quotes" |
| 838 | else |
| 839 | fail "Escaped dollar in double quotes" "\$HOME" "$result" |
| 840 | fi |
| 841 | |
| 842 | result=$("$FORTSH_BIN" -c "echo '\$HOME'" 2>&1) |
| 843 | if [ "$result" = '$HOME' ]; then |
| 844 | pass "Dollar in single quotes" |
| 845 | else |
| 846 | fail "Dollar in single quotes" "\$HOME" "$result" |
| 847 | fi |
| 848 | |
| 849 | # ===================================== |
| 850 | section "476. BACKSLASH IN QUOTES" |
| 851 | # ===================================== |
| 852 | |
| 853 | result=$("$FORTSH_BIN" -c 'echo "\\"' 2>&1) |
| 854 | if [ "$result" = '\' ]; then |
| 855 | pass "Escaped backslash in double quotes" |
| 856 | else |
| 857 | fail "Escaped backslash in double quotes" "\\" "$result" |
| 858 | fi |
| 859 | |
| 860 | result=$("$FORTSH_BIN" -c "echo '\\'" 2>&1) |
| 861 | if [ "$result" = '\' ]; then |
| 862 | pass "Backslash in single quotes" |
| 863 | else |
| 864 | fail "Backslash in single quotes" "\\" "$result" |
| 865 | fi |
| 866 | |
| 867 | # ===================================== |
| 868 | section "477. QUOTE REMOVAL" |
| 869 | # ===================================== |
| 870 | |
| 871 | result=$("$FORTSH_BIN" -c 'echo ""hello""' 2>&1) |
| 872 | if [ "$result" = "hello" ]; then |
| 873 | pass "Empty quotes around word" |
| 874 | else |
| 875 | fail "Empty quotes around word" "hello" "$result" |
| 876 | fi |
| 877 | |
| 878 | result=$("$FORTSH_BIN" -c 'echo "a""b""c"' 2>&1) |
| 879 | if [ "$result" = "abc" ]; then |
| 880 | pass "Adjacent quoted strings concatenate" |
| 881 | else |
| 882 | fail "Adjacent quoted strings concatenate" "abc" "$result" |
| 883 | fi |
| 884 | |
| 885 | # ===================================== |
| 886 | section "478. MIXED QUOTE STYLES" |
| 887 | # ===================================== |
| 888 | |
| 889 | result=$("$FORTSH_BIN" -c "x=val; echo 'a'\"\$x\"'b'" 2>&1) |
| 890 | if [ "$result" = "avalb" ]; then |
| 891 | pass "Mixed single and double quotes" |
| 892 | else |
| 893 | fail "Mixed single and double quotes" "avalb" "$result" |
| 894 | fi |
| 895 | |
| 896 | result=$("$FORTSH_BIN" -c "echo 'single'\"double\"'single'" 2>&1) |
| 897 | if [ "$result" = "singledoublesingle" ]; then |
| 898 | pass "Alternating quote styles" |
| 899 | else |
| 900 | fail "Alternating quote styles" "singledoublesingle" "$result" |
| 901 | fi |
| 902 | |
| 903 | # ===================================== |
| 904 | section "479. QUOTING IN REDIRECTIONS" |
| 905 | # ===================================== |
| 906 | |
| 907 | result=$("$FORTSH_BIN" -c 'echo test > "/tmp/quote space test.txt"; cat "/tmp/quote space test.txt"; rm "/tmp/quote space test.txt"' 2>&1) |
| 908 | if [ "$result" = "test" ]; then |
| 909 | pass "Quoted filename with space in redirect" |
| 910 | else |
| 911 | fail "Quoted filename with space in redirect" "test" "$result" |
| 912 | fi |
| 913 | |
| 914 | # ===================================== |
| 915 | section "480. EMPTY QUOTES" |
| 916 | # ===================================== |
| 917 | |
| 918 | result=$("$FORTSH_BIN" -c 'echo ""' 2>&1) |
| 919 | if [ -z "$result" ]; then |
| 920 | pass "Empty double quotes produce empty output" |
| 921 | else |
| 922 | fail "Empty double quotes produce empty output" "empty" "$result" |
| 923 | fi |
| 924 | |
| 925 | result=$("$FORTSH_BIN" -c "echo ''" 2>&1) |
| 926 | if [ -z "$result" ]; then |
| 927 | pass "Empty single quotes produce empty output" |
| 928 | else |
| 929 | fail "Empty single quotes produce empty output" "empty" "$result" |
| 930 | fi |
| 931 | |
| 932 | result=$("$FORTSH_BIN" -c 'x=""; echo "[$x]"' 2>&1) |
| 933 | if [ "$result" = "[]" ]; then |
| 934 | pass "Empty var in quotes" |
| 935 | else |
| 936 | fail "Empty var in quotes" "[]" "$result" |
| 937 | fi |
| 938 | |
| 939 | # ===================================== |
| 940 | # Summary |
| 941 | # ===================================== |
| 942 | printf "\n" |
| 943 | printf "${BLUE}==========================================\n" |
| 944 | printf "POSIX Quoting Test Summary\n" |
| 945 | printf "==========================================${NC}\n" |
| 946 | printf "Passed: ${GREEN}%d${NC}\n" "$PASSED" |
| 947 | printf "Failed: ${RED}%d${NC}\n" "$FAILED" |
| 948 | printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED" |
| 949 | printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))" |
| 950 | |
| 951 | if [ -n "$FAILED_TESTS_LIST" ]; then |
| 952 | printf "\n${RED}Failed tests:${NC}\n" |
| 953 | printf "%b" "$FAILED_TESTS_LIST" |
| 954 | fi |
| 955 | |
| 956 | if [ "$FAILED" -gt 0 ]; then |
| 957 | exit 1 |
| 958 | fi |
| 959 | exit 0 |