| 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 | if [ "$result" = "hello'world'" ]; then |
| 203 | pass "Adjacent double and single quotes" |
| 204 | else |
| 205 | fail "Adjacent double and single quotes" "hello'world'" "$result" |
| 206 | fi |
| 207 | |
| 208 | result=$("$FORTSH_BIN" -c 'x=val; echo "$x"'\''$x'\''' 2>&1) |
| 209 | if [ "$result" = 'val$x' ]; then |
| 210 | pass "Mixed expansion and literal" |
| 211 | else |
| 212 | fail "Mixed expansion and literal" 'val$x' "$result" |
| 213 | fi |
| 214 | |
| 215 | result=$("$FORTSH_BIN" -c "echo 'don'\\''t'" 2>&1) |
| 216 | if [ "$result" = "don't" ]; then |
| 217 | pass "Single quote in single quoted string" |
| 218 | else |
| 219 | fail "Single quote in single quoted string" "don't" "$result" |
| 220 | fi |
| 221 | |
| 222 | # ===================================== |
| 223 | section "450. QUOTING SPECIAL CHARACTERS" |
| 224 | # ===================================== |
| 225 | |
| 226 | result=$("$FORTSH_BIN" -c 'echo "semi;colon"' 2>&1) |
| 227 | if [ "$result" = "semi;colon" ]; then |
| 228 | pass "Semicolon in double quotes" |
| 229 | else |
| 230 | fail "Semicolon in double quotes" "semi;colon" "$result" |
| 231 | fi |
| 232 | |
| 233 | result=$("$FORTSH_BIN" -c "echo 'pipe|char'" 2>&1) |
| 234 | if [ "$result" = "pipe|char" ]; then |
| 235 | pass "Pipe in single quotes" |
| 236 | else |
| 237 | fail "Pipe in single quotes" "pipe|char" "$result" |
| 238 | fi |
| 239 | |
| 240 | result=$("$FORTSH_BIN" -c 'echo "ampersand&here"' 2>&1) |
| 241 | if [ "$result" = "ampersand&here" ]; then |
| 242 | pass "Ampersand in double quotes" |
| 243 | else |
| 244 | fail "Ampersand in double quotes" "ampersand&here" "$result" |
| 245 | fi |
| 246 | |
| 247 | result=$("$FORTSH_BIN" -c "echo 'less<greater>'" 2>&1) |
| 248 | if [ "$result" = "less<greater>" ]; then |
| 249 | pass "Angle brackets in single quotes" |
| 250 | else |
| 251 | fail "Angle brackets in single quotes" "less<greater>" "$result" |
| 252 | fi |
| 253 | |
| 254 | result=$("$FORTSH_BIN" -c 'echo "paren(here)"' 2>&1) |
| 255 | if [ "$result" = "paren(here)" ]; then |
| 256 | pass "Parentheses in double quotes" |
| 257 | else |
| 258 | fail "Parentheses in double quotes" "paren(here)" "$result" |
| 259 | fi |
| 260 | |
| 261 | result=$("$FORTSH_BIN" -c "echo 'star*glob?'" 2>&1) |
| 262 | if [ "$result" = "star*glob?" ]; then |
| 263 | pass "Glob chars in single quotes" |
| 264 | else |
| 265 | fail "Glob chars in single quotes" "star*glob?" "$result" |
| 266 | fi |
| 267 | |
| 268 | result=$("$FORTSH_BIN" -c 'echo "hash#comment"' 2>&1) |
| 269 | if [ "$result" = "hash#comment" ]; then |
| 270 | pass "Hash in double quotes" |
| 271 | else |
| 272 | fail "Hash in double quotes" "hash#comment" "$result" |
| 273 | fi |
| 274 | |
| 275 | # ===================================== |
| 276 | section "451. EMPTY STRINGS" |
| 277 | # ===================================== |
| 278 | |
| 279 | result=$("$FORTSH_BIN" -c 'echo ""' 2>&1) |
| 280 | if [ "$result" = "" ]; then |
| 281 | pass "Empty double-quoted string" |
| 282 | else |
| 283 | fail "Empty double-quoted string" "(empty)" "$result" |
| 284 | fi |
| 285 | |
| 286 | result=$("$FORTSH_BIN" -c "echo ''" 2>&1) |
| 287 | if [ "$result" = "" ]; then |
| 288 | pass "Empty single-quoted string" |
| 289 | else |
| 290 | fail "Empty single-quoted string" "(empty)" "$result" |
| 291 | fi |
| 292 | |
| 293 | result=$("$FORTSH_BIN" -c 'x=""; echo "[$x]"' 2>&1) |
| 294 | if [ "$result" = "[]" ]; then |
| 295 | pass "Empty variable in quotes" |
| 296 | else |
| 297 | fail "Empty variable in quotes" "[]" "$result" |
| 298 | fi |
| 299 | |
| 300 | result=$("$FORTSH_BIN" -c 'echo a "" b' 2>&1) |
| 301 | if [ "$result" = "a b" ]; then |
| 302 | pass "Empty string preserves word" |
| 303 | else |
| 304 | fail "Empty string preserves word" "a b" "$result" |
| 305 | fi |
| 306 | |
| 307 | # ===================================== |
| 308 | section "452. WORD SPLITTING" |
| 309 | # ===================================== |
| 310 | |
| 311 | result=$("$FORTSH_BIN" -c 'x="a b c"; for w in $x; do echo "[$w]"; done' 2>&1) |
| 312 | expected=$(printf "[a]\n[b]\n[c]") |
| 313 | if [ "$result" = "$expected" ]; then |
| 314 | pass "Unquoted variable splits on whitespace" |
| 315 | else |
| 316 | fail "Unquoted variable splits on whitespace" "$expected" "$result" |
| 317 | fi |
| 318 | |
| 319 | result=$("$FORTSH_BIN" -c 'x="a b c"; for w in "$x"; do echo "[$w]"; done' 2>&1) |
| 320 | if [ "$result" = "[a b c]" ]; then |
| 321 | pass "Quoted variable prevents splitting" |
| 322 | else |
| 323 | fail "Quoted variable prevents splitting" "[a b c]" "$result" |
| 324 | fi |
| 325 | |
| 326 | result=$("$FORTSH_BIN" -c 'x="a b"; echo $x' 2>&1) |
| 327 | if [ "$result" = "a b" ]; then |
| 328 | pass "Word splitting collapses spaces" |
| 329 | else |
| 330 | fail "Word splitting collapses spaces" "a b" "$result" |
| 331 | fi |
| 332 | |
| 333 | result=$("$FORTSH_BIN" -c 'x="a b"; echo "$x"' 2>&1) |
| 334 | if [ "$result" = "a b" ]; then |
| 335 | pass "Quoting preserves multiple spaces" |
| 336 | else |
| 337 | fail "Quoting preserves multiple spaces" "a b" "$result" |
| 338 | fi |
| 339 | |
| 340 | # ===================================== |
| 341 | section "453. GLOB PREVENTION" |
| 342 | # ===================================== |
| 343 | |
| 344 | result=$("$FORTSH_BIN" -c 'echo "*"' 2>&1) |
| 345 | if [ "$result" = "*" ]; then |
| 346 | pass "Double quotes prevent glob expansion" |
| 347 | else |
| 348 | fail "Double quotes prevent glob expansion" "*" "$result" |
| 349 | fi |
| 350 | |
| 351 | result=$("$FORTSH_BIN" -c "echo '[a-z]*'" 2>&1) |
| 352 | if [ "$result" = "[a-z]*" ]; then |
| 353 | pass "Single quotes prevent bracket expansion" |
| 354 | else |
| 355 | fail "Single quotes prevent bracket expansion" "[a-z]*" "$result" |
| 356 | fi |
| 357 | |
| 358 | result=$("$FORTSH_BIN" -c 'echo "?"' 2>&1) |
| 359 | if [ "$result" = "?" ]; then |
| 360 | pass "Double quotes prevent ? expansion" |
| 361 | else |
| 362 | fail "Double quotes prevent ? expansion" "?" "$result" |
| 363 | fi |
| 364 | |
| 365 | # ===================================== |
| 366 | section "454. QUOTE IN VARIABLE" |
| 367 | # ===================================== |
| 368 | |
| 369 | result=$("$FORTSH_BIN" -c "x=\"has 'quotes'\"; echo \"\$x\"" 2>&1) |
| 370 | if [ "$result" = "has 'quotes'" ]; then |
| 371 | pass "Single quotes inside double-quoted variable" |
| 372 | else |
| 373 | fail "Single quotes inside double-quoted variable" "has 'quotes'" "$result" |
| 374 | fi |
| 375 | |
| 376 | result=$("$FORTSH_BIN" -c 'x="has \"quotes\""; echo "$x"' 2>&1) |
| 377 | if [ "$result" = 'has "quotes"' ]; then |
| 378 | pass "Double quotes inside variable" |
| 379 | else |
| 380 | fail "Double quotes inside variable" 'has "quotes"' "$result" |
| 381 | fi |
| 382 | |
| 383 | # ===================================== |
| 384 | section "455. QUOTING IN ASSIGNMENTS" |
| 385 | # ===================================== |
| 386 | |
| 387 | result=$("$FORTSH_BIN" -c 'x="hello world"; echo $x' 2>&1) |
| 388 | if [ "$result" = "hello world" ]; then |
| 389 | pass "Quoted assignment with spaces" |
| 390 | else |
| 391 | fail "Quoted assignment with spaces" "hello world" "$result" |
| 392 | fi |
| 393 | |
| 394 | result=$("$FORTSH_BIN" -c "x='no \$expansion'; echo \"\$x\"" 2>&1) |
| 395 | if [ "$result" = 'no $expansion' ]; then |
| 396 | pass "Single-quoted assignment" |
| 397 | else |
| 398 | fail "Single-quoted assignment" 'no $expansion' "$result" |
| 399 | fi |
| 400 | |
| 401 | result=$("$FORTSH_BIN" -c 'y=value; x="with $y"; echo "$x"' 2>&1) |
| 402 | if [ "$result" = "with value" ]; then |
| 403 | pass "Variable expansion in assignment" |
| 404 | else |
| 405 | fail "Variable expansion in assignment" "with value" "$result" |
| 406 | fi |
| 407 | |
| 408 | # ===================================== |
| 409 | section "456. QUOTING IN COMMAND SUBSTITUTION" |
| 410 | # ===================================== |
| 411 | |
| 412 | result=$("$FORTSH_BIN" -c 'x=$(echo "hello world"); echo "$x"' 2>&1) |
| 413 | if [ "$result" = "hello world" ]; then |
| 414 | pass "Quotes inside command substitution" |
| 415 | else |
| 416 | fail "Quotes inside command substitution" "hello world" "$result" |
| 417 | fi |
| 418 | |
| 419 | result=$("$FORTSH_BIN" -c 'x="$(echo "nested quotes")"; echo "$x"' 2>&1) |
| 420 | if [ "$result" = "nested quotes" ]; then |
| 421 | pass "Nested quotes in command substitution" |
| 422 | else |
| 423 | fail "Nested quotes in command substitution" "nested quotes" "$result" |
| 424 | fi |
| 425 | |
| 426 | # ===================================== |
| 427 | section "457. BACKSLASH IN DOUBLE QUOTES" |
| 428 | # ===================================== |
| 429 | |
| 430 | result=$("$FORTSH_BIN" -c 'echo "back\\\\slash"' 2>&1) |
| 431 | if [ "$result" = 'back\slash' ]; then |
| 432 | pass "Backslash-backslash in double quotes" |
| 433 | else |
| 434 | fail "Backslash-backslash in double quotes" 'back\slash' "$result" |
| 435 | fi |
| 436 | |
| 437 | result=$("$FORTSH_BIN" -c 'echo "newline\\n"' 2>&1) |
| 438 | if [ "$result" = 'newline\n' ]; then |
| 439 | pass "Backslash-n in double quotes (literal)" |
| 440 | else |
| 441 | fail "Backslash-n in double quotes (literal)" 'newline\n' "$result" |
| 442 | fi |
| 443 | |
| 444 | result=$("$FORTSH_BIN" -c 'echo "tab\\t"' 2>&1) |
| 445 | if [ "$result" = 'tab\t' ]; then |
| 446 | pass "Backslash-t in double quotes (literal)" |
| 447 | else |
| 448 | fail "Backslash-t in double quotes (literal)" 'tab\t' "$result" |
| 449 | fi |
| 450 | |
| 451 | # ===================================== |
| 452 | section "458. DOLLAR IN QUOTES" |
| 453 | # ===================================== |
| 454 | |
| 455 | result=$("$FORTSH_BIN" -c 'echo "cost: \$5"' 2>&1) |
| 456 | if [ "$result" = 'cost: $5' ]; then |
| 457 | pass "Escaped dollar in double quotes" |
| 458 | else |
| 459 | fail "Escaped dollar in double quotes" 'cost: $5' "$result" |
| 460 | fi |
| 461 | |
| 462 | result=$("$FORTSH_BIN" -c "echo 'cost: \$5'" 2>&1) |
| 463 | if [ "$result" = 'cost: $5' ]; then |
| 464 | pass "Dollar in single quotes (literal)" |
| 465 | else |
| 466 | fail "Dollar in single quotes (literal)" 'cost: $5' "$result" |
| 467 | fi |
| 468 | |
| 469 | # ===================================== |
| 470 | section "459. NEWLINE IN QUOTES" |
| 471 | # ===================================== |
| 472 | |
| 473 | result=$("$FORTSH_BIN" -c 'echo "line1 |
| 474 | line2"' 2>&1) |
| 475 | expected=$(printf "line1\nline2") |
| 476 | if [ "$result" = "$expected" ]; then |
| 477 | pass "Literal newline in double quotes" |
| 478 | else |
| 479 | fail "Literal newline in double quotes" "$expected" "$result" |
| 480 | fi |
| 481 | |
| 482 | result=$("$FORTSH_BIN" -c "echo 'line1 |
| 483 | line2'" 2>&1) |
| 484 | expected=$(printf "line1\nline2") |
| 485 | if [ "$result" = "$expected" ]; then |
| 486 | pass "Literal newline in single quotes" |
| 487 | else |
| 488 | fail "Literal newline in single quotes" "$expected" "$result" |
| 489 | fi |
| 490 | |
| 491 | # ===================================== |
| 492 | section "460. TAB AND SPACE IN QUOTES" |
| 493 | # ===================================== |
| 494 | |
| 495 | result=$("$FORTSH_BIN" -c 'echo " tab"' 2>&1) |
| 496 | if printf "%s" "$result" | grep -q " "; then |
| 497 | pass "Literal tab in double quotes" |
| 498 | else |
| 499 | fail "Literal tab in double quotes" "string with tab" "$result" |
| 500 | fi |
| 501 | |
| 502 | result=$("$FORTSH_BIN" -c 'echo " spaces "' 2>&1) |
| 503 | if [ "$result" = " spaces " ]; then |
| 504 | pass "Multiple spaces in double quotes" |
| 505 | else |
| 506 | fail "Multiple spaces in double quotes" " spaces " "$result" |
| 507 | fi |
| 508 | |
| 509 | # ===================================== |
| 510 | section "461. QUOTING IN FOR LOOP" |
| 511 | # ===================================== |
| 512 | |
| 513 | result=$("$FORTSH_BIN" -c 'for x in "a b" c; do echo "[$x]"; done' 2>&1) |
| 514 | expected=$(printf "[a b]\n[c]") |
| 515 | if [ "$result" = "$expected" ]; then |
| 516 | pass "Quoted string in for list" |
| 517 | else |
| 518 | fail "Quoted string in for list" "$expected" "$result" |
| 519 | fi |
| 520 | |
| 521 | result=$("$FORTSH_BIN" -c 'list="a b c"; for x in $list; do echo "[$x]"; done' 2>&1) |
| 522 | expected=$(printf "[a]\n[b]\n[c]") |
| 523 | if [ "$result" = "$expected" ]; then |
| 524 | pass "Unquoted var splits in for" |
| 525 | else |
| 526 | fail "Unquoted var splits in for" "$expected" "$result" |
| 527 | fi |
| 528 | |
| 529 | result=$("$FORTSH_BIN" -c 'list="a b c"; for x in "$list"; do echo "[$x]"; done' 2>&1) |
| 530 | if [ "$result" = "[a b c]" ]; then |
| 531 | pass "Quoted var no split in for" |
| 532 | else |
| 533 | fail "Quoted var no split in for" "[a b c]" "$result" |
| 534 | fi |
| 535 | |
| 536 | # ===================================== |
| 537 | section "462. QUOTING IN CASE STATEMENT" |
| 538 | # ===================================== |
| 539 | |
| 540 | result=$("$FORTSH_BIN" -c 'x="hello world"; case "$x" in "hello world") echo match;; esac' 2>&1) |
| 541 | if [ "$result" = "match" ]; then |
| 542 | pass "Quoted string in case word" |
| 543 | else |
| 544 | fail "Quoted string in case word" "match" "$result" |
| 545 | fi |
| 546 | |
| 547 | result=$("$FORTSH_BIN" -c 'case "a b" in "a b") echo yes;; *) echo no;; esac' 2>&1) |
| 548 | if [ "$result" = "yes" ]; then |
| 549 | pass "Quoted pattern in case" |
| 550 | else |
| 551 | fail "Quoted pattern in case" "yes" "$result" |
| 552 | fi |
| 553 | |
| 554 | # ===================================== |
| 555 | section "463. QUOTING SPECIAL SHELL CHARS" |
| 556 | # ===================================== |
| 557 | |
| 558 | result=$("$FORTSH_BIN" -c 'echo "hello; world"' 2>&1) |
| 559 | if [ "$result" = "hello; world" ]; then |
| 560 | pass "Semicolon in double quotes" |
| 561 | else |
| 562 | fail "Semicolon in double quotes" "hello; world" "$result" |
| 563 | fi |
| 564 | |
| 565 | result=$("$FORTSH_BIN" -c "echo 'a && b'" 2>&1) |
| 566 | if [ "$result" = "a && b" ]; then |
| 567 | pass "Double ampersand in single quotes" |
| 568 | else |
| 569 | fail "Double ampersand in single quotes" "a && b" "$result" |
| 570 | fi |
| 571 | |
| 572 | result=$("$FORTSH_BIN" -c 'echo "a || b"' 2>&1) |
| 573 | if [ "$result" = "a || b" ]; then |
| 574 | pass "Double pipe in double quotes" |
| 575 | else |
| 576 | fail "Double pipe in double quotes" "a || b" "$result" |
| 577 | fi |
| 578 | |
| 579 | result=$("$FORTSH_BIN" -c "echo 'back\`tick'" 2>&1) |
| 580 | if [ "$result" = 'back`tick' ]; then |
| 581 | pass "Backtick in single quotes" |
| 582 | else |
| 583 | fail "Backtick in single quotes" 'back`tick' "$result" |
| 584 | fi |
| 585 | |
| 586 | # ===================================== |
| 587 | section "464. QUOTING IN ARITHMETIC" |
| 588 | # ===================================== |
| 589 | |
| 590 | result=$("$FORTSH_BIN" -c 'x=5; echo $((x + 3))' 2>&1) |
| 591 | if [ "$result" = "8" ]; then |
| 592 | pass "Unquoted var in arithmetic" |
| 593 | else |
| 594 | fail "Unquoted var in arithmetic" "8" "$result" |
| 595 | fi |
| 596 | |
| 597 | result=$("$FORTSH_BIN" -c 'x="5"; echo $((x + 3))' 2>&1) |
| 598 | if [ "$result" = "8" ]; then |
| 599 | pass "Quoted assignment used in arithmetic" |
| 600 | else |
| 601 | fail "Quoted assignment used in arithmetic" "8" "$result" |
| 602 | fi |
| 603 | |
| 604 | # ===================================== |
| 605 | section "465. ESCAPE SEQUENCES" |
| 606 | # ===================================== |
| 607 | |
| 608 | result=$("$FORTSH_BIN" -c 'echo "hello\tworld"' 2>&1) |
| 609 | if echo "$result" | grep -q "hello"; then |
| 610 | pass "Backslash-t in double quotes" |
| 611 | else |
| 612 | fail "Backslash-t in double quotes" |
| 613 | fi |
| 614 | |
| 615 | result=$("$FORTSH_BIN" -c "echo 'hello\nworld'" 2>&1) |
| 616 | if echo "$result" | grep -q 'hello\\nworld'; then |
| 617 | pass "Backslash-n literal in single quotes" |
| 618 | else |
| 619 | fail "Backslash-n literal in single quotes" |
| 620 | fi |
| 621 | |
| 622 | # ===================================== |
| 623 | section "466. QUOTING AND WORD SPLITTING" |
| 624 | # ===================================== |
| 625 | |
| 626 | result=$("$FORTSH_BIN" -c 'x="a b c"; set -- $x; echo $#' 2>&1) |
| 627 | if [ "$result" = "3" ]; then |
| 628 | pass "Unquoted var splits on spaces" |
| 629 | else |
| 630 | fail "Unquoted var splits on spaces" "3" "$result" |
| 631 | fi |
| 632 | |
| 633 | result=$("$FORTSH_BIN" -c 'x="a b c"; set -- "$x"; echo $#' 2>&1) |
| 634 | if [ "$result" = "1" ]; then |
| 635 | pass "Quoted var prevents splitting" |
| 636 | else |
| 637 | fail "Quoted var prevents splitting" "1" "$result" |
| 638 | fi |
| 639 | |
| 640 | result=$("$FORTSH_BIN" -c 'x=""; set -- $x; echo $#' 2>&1) |
| 641 | if [ "$result" = "0" ]; then |
| 642 | pass "Empty unquoted var produces no args" |
| 643 | else |
| 644 | fail "Empty unquoted var produces no args" "0" "$result" |
| 645 | fi |
| 646 | |
| 647 | result=$("$FORTSH_BIN" -c 'x=""; set -- "$x"; echo $#' 2>&1) |
| 648 | if [ "$result" = "1" ]; then |
| 649 | pass "Empty quoted var produces one empty arg" |
| 650 | else |
| 651 | fail "Empty quoted var produces one empty arg" "1" "$result" |
| 652 | fi |
| 653 | |
| 654 | # ===================================== |
| 655 | section "467. QUOTING SPECIAL PARAMETERS" |
| 656 | # ===================================== |
| 657 | |
| 658 | result=$("$FORTSH_BIN" -c 'set -- a b c; echo "$@" | wc -w' 2>&1) |
| 659 | if [ "$result" = "3" ]; then |
| 660 | pass 'Quoted $@ preserves args' |
| 661 | else |
| 662 | fail 'Quoted $@ preserves args' "3" "$result" |
| 663 | fi |
| 664 | |
| 665 | result=$("$FORTSH_BIN" -c 'set -- a b c; echo "$*"' 2>&1) |
| 666 | if [ "$result" = "a b c" ]; then |
| 667 | pass 'Quoted $* joins args' |
| 668 | else |
| 669 | fail 'Quoted $* joins args' "a b c" "$result" |
| 670 | fi |
| 671 | |
| 672 | result=$("$FORTSH_BIN" -c 'set -- a "b c" d; for x in "$@"; do echo "[$x]"; done' 2>&1) |
| 673 | expected=$(printf "[a]\n[b c]\n[d]") |
| 674 | if [ "$result" = "$expected" ]; then |
| 675 | pass 'Quoted $@ preserves quoting in original args' |
| 676 | else |
| 677 | fail 'Quoted $@ preserves quoting in original args' |
| 678 | fi |
| 679 | |
| 680 | # ===================================== |
| 681 | section "468. NESTED QUOTING" |
| 682 | # ===================================== |
| 683 | |
| 684 | result=$("$FORTSH_BIN" -c "echo 'single \"with\" double'" 2>&1) |
| 685 | if [ "$result" = 'single "with" double' ]; then |
| 686 | pass "Double quotes inside single quotes" |
| 687 | else |
| 688 | fail "Double quotes inside single quotes" |
| 689 | fi |
| 690 | |
| 691 | result=$("$FORTSH_BIN" -c 'echo "double '\''with'\'' single"' 2>&1) |
| 692 | if [ "$result" = "double 'with' single" ]; then |
| 693 | pass "Single quotes inside double quotes (escaped)" |
| 694 | else |
| 695 | fail "Single quotes inside double quotes (escaped)" |
| 696 | fi |
| 697 | |
| 698 | # ===================================== |
| 699 | section "469. QUOTING IN ASSIGNMENTS" |
| 700 | # ===================================== |
| 701 | |
| 702 | result=$("$FORTSH_BIN" -c 'x="hello world"; echo $x' 2>&1) |
| 703 | if [ "$result" = "hello world" ]; then |
| 704 | pass "Quoted assignment with spaces" |
| 705 | else |
| 706 | fail "Quoted assignment with spaces" "hello world" "$result" |
| 707 | fi |
| 708 | |
| 709 | result=$("$FORTSH_BIN" -c "x='hello world'; echo \$x" 2>&1) |
| 710 | if [ "$result" = "hello world" ]; then |
| 711 | pass "Single-quoted assignment with spaces" |
| 712 | else |
| 713 | fail "Single-quoted assignment with spaces" "hello world" "$result" |
| 714 | fi |
| 715 | |
| 716 | result=$("$FORTSH_BIN" -c 'x=hello\ world; echo $x' 2>&1) |
| 717 | if [ "$result" = "hello world" ]; then |
| 718 | pass "Escaped space in assignment" |
| 719 | else |
| 720 | fail "Escaped space in assignment" "hello world" "$result" |
| 721 | fi |
| 722 | |
| 723 | # ===================================== |
| 724 | section "470. QUOTING IN COMMAND SUBSTITUTION" |
| 725 | # ===================================== |
| 726 | |
| 727 | result=$("$FORTSH_BIN" -c 'x=$(echo "hello world"); echo "$x"' 2>&1) |
| 728 | if [ "$result" = "hello world" ]; then |
| 729 | pass "Quoted string in command substitution" |
| 730 | else |
| 731 | fail "Quoted string in command substitution" "hello world" "$result" |
| 732 | fi |
| 733 | |
| 734 | result=$("$FORTSH_BIN" -c 'x=`echo "hello world"`; echo "$x"' 2>&1) |
| 735 | if [ "$result" = "hello world" ]; then |
| 736 | pass "Quoted string in backtick substitution" |
| 737 | else |
| 738 | fail "Quoted string in backtick substitution" "hello world" "$result" |
| 739 | fi |
| 740 | |
| 741 | # ===================================== |
| 742 | section "471. QUOTING AND GLOB PREVENTION" |
| 743 | # ===================================== |
| 744 | |
| 745 | result=$("$FORTSH_BIN" -c 'echo "*"' 2>&1) |
| 746 | if [ "$result" = "*" ]; then |
| 747 | pass "Quoted asterisk is literal" |
| 748 | else |
| 749 | fail "Quoted asterisk is literal" "*" "$result" |
| 750 | fi |
| 751 | |
| 752 | result=$("$FORTSH_BIN" -c 'echo "?"' 2>&1) |
| 753 | if [ "$result" = "?" ]; then |
| 754 | pass "Quoted question mark is literal" |
| 755 | else |
| 756 | fail "Quoted question mark is literal" "?" "$result" |
| 757 | fi |
| 758 | |
| 759 | result=$("$FORTSH_BIN" -c 'echo "[abc]"' 2>&1) |
| 760 | if [ "$result" = "[abc]" ]; then |
| 761 | pass "Quoted brackets are literal" |
| 762 | else |
| 763 | fail "Quoted brackets are literal" "[abc]" "$result" |
| 764 | fi |
| 765 | |
| 766 | # ===================================== |
| 767 | section "472. QUOTING IN TESTS" |
| 768 | # ===================================== |
| 769 | |
| 770 | result=$("$FORTSH_BIN" -c 'x=""; [ -z "$x" ] && echo empty' 2>&1) |
| 771 | if [ "$result" = "empty" ]; then |
| 772 | pass "Quoted empty var in test -z" |
| 773 | else |
| 774 | fail "Quoted empty var in test -z" "empty" "$result" |
| 775 | fi |
| 776 | |
| 777 | result=$("$FORTSH_BIN" -c 'x="hello"; [ -n "$x" ] && echo nonempty' 2>&1) |
| 778 | if [ "$result" = "nonempty" ]; then |
| 779 | pass "Quoted var in test -n" |
| 780 | else |
| 781 | fail "Quoted var in test -n" "nonempty" "$result" |
| 782 | fi |
| 783 | |
| 784 | result=$("$FORTSH_BIN" -c 'x="a b"; [ "$x" = "a b" ] && echo match' 2>&1) |
| 785 | if [ "$result" = "match" ]; then |
| 786 | pass "Quoted var with spaces in test =" |
| 787 | else |
| 788 | fail "Quoted var with spaces in test =" "match" "$result" |
| 789 | fi |
| 790 | |
| 791 | # ===================================== |
| 792 | section "473. QUOTING IN CASE PATTERNS" |
| 793 | # ===================================== |
| 794 | |
| 795 | result=$("$FORTSH_BIN" -c 'x="*"; case "$x" in "*") echo literal;; esac' 2>&1) |
| 796 | if [ "$result" = "literal" ]; then |
| 797 | pass "Quoted asterisk matches literally in case" |
| 798 | else |
| 799 | fail "Quoted asterisk matches literally in case" "literal" "$result" |
| 800 | fi |
| 801 | |
| 802 | result=$("$FORTSH_BIN" -c 'x="a b"; case "$x" in "a b") echo match;; esac' 2>&1) |
| 803 | if [ "$result" = "match" ]; then |
| 804 | pass "Quoted pattern with space in case" |
| 805 | else |
| 806 | fail "Quoted pattern with space in case" "match" "$result" |
| 807 | fi |
| 808 | |
| 809 | # ===================================== |
| 810 | section "474. QUOTING IN FUNCTION ARGS" |
| 811 | # ===================================== |
| 812 | |
| 813 | result=$("$FORTSH_BIN" -c 'f() { echo "[$1]"; }; f "hello world"' 2>&1) |
| 814 | if [ "$result" = "[hello world]" ]; then |
| 815 | pass "Quoted arg to function" |
| 816 | else |
| 817 | fail "Quoted arg to function" "[hello world]" "$result" |
| 818 | fi |
| 819 | |
| 820 | result=$("$FORTSH_BIN" -c 'f() { echo $#; }; f "a b" "c d"' 2>&1) |
| 821 | if [ "$result" = "2" ]; then |
| 822 | pass "Quoted args count in function" |
| 823 | else |
| 824 | fail "Quoted args count in function" "2" "$result" |
| 825 | fi |
| 826 | |
| 827 | # ===================================== |
| 828 | section "475. DOLLAR IN QUOTES" |
| 829 | # ===================================== |
| 830 | |
| 831 | result=$("$FORTSH_BIN" -c 'echo "\$HOME"' 2>&1) |
| 832 | if [ "$result" = '$HOME' ]; then |
| 833 | pass "Escaped dollar in double quotes" |
| 834 | else |
| 835 | fail "Escaped dollar in double quotes" "\$HOME" "$result" |
| 836 | fi |
| 837 | |
| 838 | result=$("$FORTSH_BIN" -c "echo '\$HOME'" 2>&1) |
| 839 | if [ "$result" = '$HOME' ]; then |
| 840 | pass "Dollar in single quotes" |
| 841 | else |
| 842 | fail "Dollar in single quotes" "\$HOME" "$result" |
| 843 | fi |
| 844 | |
| 845 | # ===================================== |
| 846 | section "476. BACKSLASH IN QUOTES" |
| 847 | # ===================================== |
| 848 | |
| 849 | result=$("$FORTSH_BIN" -c 'echo "\\"' 2>&1) |
| 850 | if [ "$result" = '\' ]; then |
| 851 | pass "Escaped backslash in double quotes" |
| 852 | else |
| 853 | fail "Escaped backslash in double quotes" "\\" "$result" |
| 854 | fi |
| 855 | |
| 856 | result=$("$FORTSH_BIN" -c "echo '\\'" 2>&1) |
| 857 | if [ "$result" = '\' ]; then |
| 858 | pass "Backslash in single quotes" |
| 859 | else |
| 860 | fail "Backslash in single quotes" "\\" "$result" |
| 861 | fi |
| 862 | |
| 863 | # ===================================== |
| 864 | section "477. QUOTE REMOVAL" |
| 865 | # ===================================== |
| 866 | |
| 867 | result=$("$FORTSH_BIN" -c 'echo ""hello""' 2>&1) |
| 868 | if [ "$result" = "hello" ]; then |
| 869 | pass "Empty quotes around word" |
| 870 | else |
| 871 | fail "Empty quotes around word" "hello" "$result" |
| 872 | fi |
| 873 | |
| 874 | result=$("$FORTSH_BIN" -c 'echo "a""b""c"' 2>&1) |
| 875 | if [ "$result" = "abc" ]; then |
| 876 | pass "Adjacent quoted strings concatenate" |
| 877 | else |
| 878 | fail "Adjacent quoted strings concatenate" "abc" "$result" |
| 879 | fi |
| 880 | |
| 881 | # ===================================== |
| 882 | section "478. MIXED QUOTE STYLES" |
| 883 | # ===================================== |
| 884 | |
| 885 | result=$("$FORTSH_BIN" -c "x=val; echo 'a'\"\$x\"'b'" 2>&1) |
| 886 | if [ "$result" = "avalb" ]; then |
| 887 | pass "Mixed single and double quotes" |
| 888 | else |
| 889 | fail "Mixed single and double quotes" "avalb" "$result" |
| 890 | fi |
| 891 | |
| 892 | result=$("$FORTSH_BIN" -c "echo 'single'\"double\"'single'" 2>&1) |
| 893 | if [ "$result" = "singledoublesingle" ]; then |
| 894 | pass "Alternating quote styles" |
| 895 | else |
| 896 | fail "Alternating quote styles" "singledoublesingle" "$result" |
| 897 | fi |
| 898 | |
| 899 | # ===================================== |
| 900 | section "479. QUOTING IN REDIRECTIONS" |
| 901 | # ===================================== |
| 902 | |
| 903 | 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) |
| 904 | if [ "$result" = "test" ]; then |
| 905 | pass "Quoted filename with space in redirect" |
| 906 | else |
| 907 | fail "Quoted filename with space in redirect" "test" "$result" |
| 908 | fi |
| 909 | |
| 910 | # ===================================== |
| 911 | section "480. EMPTY QUOTES" |
| 912 | # ===================================== |
| 913 | |
| 914 | result=$("$FORTSH_BIN" -c 'echo ""' 2>&1) |
| 915 | if [ -z "$result" ]; then |
| 916 | pass "Empty double quotes produce empty output" |
| 917 | else |
| 918 | fail "Empty double quotes produce empty output" "empty" "$result" |
| 919 | fi |
| 920 | |
| 921 | result=$("$FORTSH_BIN" -c "echo ''" 2>&1) |
| 922 | if [ -z "$result" ]; then |
| 923 | pass "Empty single quotes produce empty output" |
| 924 | else |
| 925 | fail "Empty single quotes produce empty output" "empty" "$result" |
| 926 | fi |
| 927 | |
| 928 | result=$("$FORTSH_BIN" -c 'x=""; echo "[$x]"' 2>&1) |
| 929 | if [ "$result" = "[]" ]; then |
| 930 | pass "Empty var in quotes" |
| 931 | else |
| 932 | fail "Empty var in quotes" "[]" "$result" |
| 933 | fi |
| 934 | |
| 935 | # ===================================== |
| 936 | # Summary |
| 937 | # ===================================== |
| 938 | printf "\n" |
| 939 | printf "${BLUE}==========================================\n" |
| 940 | printf "POSIX Quoting Test Summary\n" |
| 941 | printf "==========================================${NC}\n" |
| 942 | printf "Passed: ${GREEN}%d${NC}\n" "$PASSED" |
| 943 | printf "Failed: ${RED}%d${NC}\n" "$FAILED" |
| 944 | printf "Skipped: ${YELLOW}%d${NC}\n" "$SKIPPED" |
| 945 | printf "Total: %d\n" "$((PASSED + FAILED + SKIPPED))" |
| 946 | |
| 947 | if [ -n "$FAILED_TESTS_LIST" ]; then |
| 948 | printf "\n${RED}Failed tests:${NC}\n" |
| 949 | printf "%b" "$FAILED_TESTS_LIST" |
| 950 | fi |
| 951 | |
| 952 | if [ "$FAILED" -gt 0 ]; then |
| 953 | exit 1 |
| 954 | fi |
| 955 | exit 0 |