@@ -188,6 +188,269 @@ suite('splitCommandLineIntoSubCommands', () => {
188
188
} ) ;
189
189
} ) ;
190
190
191
+ suite ( 'redirection tests' , ( ) => {
192
+ suite ( 'output redirection' , ( ) => {
193
+ test ( 'should split on basic output redirection' , ( ) => {
194
+ const commandLine = 'echo hello > output.txt' ;
195
+ const expectedSubCommands = [ 'echo hello' , 'output.txt' ] ;
196
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
197
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
198
+ } ) ;
199
+
200
+ test ( 'should split on append redirection' , ( ) => {
201
+ const commandLine = 'echo hello >> output.txt' ;
202
+ const expectedSubCommands = [ 'echo hello' , 'output.txt' ] ;
203
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
204
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
205
+ } ) ;
206
+
207
+ test ( 'should split on multiple output redirections' , ( ) => {
208
+ const commandLine = 'ls > files.txt && cat files.txt > backup.txt' ;
209
+ const expectedSubCommands = [ 'ls' , 'files.txt' , 'cat files.txt' , 'backup.txt' ] ;
210
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
211
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
212
+ } ) ;
213
+
214
+ test ( 'should split on numbered file descriptor redirection' , ( ) => {
215
+ const commandLine = 'command 1> stdout.txt 2> stderr.txt' ;
216
+ const expectedSubCommands = [ 'command' , 'stdout.txt' , 'stderr.txt' ] ;
217
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
218
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
219
+ } ) ;
220
+
221
+ test ( 'should split on stderr-only redirection' , ( ) => {
222
+ const commandLine = 'make 2> errors.log' ;
223
+ const expectedSubCommands = [ 'make' , 'errors.log' ] ;
224
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
225
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
226
+ } ) ;
227
+
228
+ test ( 'should split on all output redirection (&>)' , ( ) => {
229
+ const commandLine = 'command &> all_output.txt' ;
230
+ const expectedSubCommands = [ 'command' , 'all_output.txt' ] ;
231
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
232
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
233
+ } ) ;
234
+ } ) ;
235
+
236
+ suite ( 'input redirection' , ( ) => {
237
+ test ( 'should split on input redirection' , ( ) => {
238
+ const commandLine = 'sort < input.txt' ;
239
+ const expectedSubCommands = [ 'sort' , 'input.txt' ] ;
240
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
241
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
242
+ } ) ;
243
+
244
+ test ( 'should split on numbered input redirection' , ( ) => {
245
+ const commandLine = 'program 0< input.txt' ;
246
+ const expectedSubCommands = [ 'program' , 'input.txt' ] ;
247
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
248
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
249
+ } ) ;
250
+
251
+ test ( 'should split on input/output combined' , ( ) => {
252
+ const commandLine = 'sort < input.txt > output.txt' ;
253
+ const expectedSubCommands = [ 'sort' , 'input.txt' , 'output.txt' ] ;
254
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
255
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
256
+ } ) ;
257
+ } ) ;
258
+
259
+ suite ( 'stream redirection' , ( ) => {
260
+ test ( 'should split on stdout to stderr redirection' , ( ) => {
261
+ const commandLine = 'echo error 1>&2' ;
262
+ const expectedSubCommands = [ 'echo error' , '' ] ;
263
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
264
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
265
+ } ) ;
266
+
267
+ test ( 'should split on stderr to stdout redirection' , ( ) => {
268
+ const commandLine = 'command 2>&1' ;
269
+ const expectedSubCommands = [ 'command' , '' ] ;
270
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
271
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
272
+ } ) ;
273
+
274
+ test ( 'should split on stream redirection with numbered descriptors' , ( ) => {
275
+ const commandLine = 'exec 3>&1 && exec 4>&2' ;
276
+ const expectedSubCommands = [ 'exec' , '' , 'exec' , '' ] ;
277
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
278
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
279
+ } ) ;
280
+
281
+ test ( 'should split on multiple stream redirections' , ( ) => {
282
+ const commandLine = 'command 2>&1 1>&3 3>&2' ;
283
+ const expectedSubCommands = [ 'command' , '' , '' , '' ] ;
284
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
285
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
286
+ } ) ;
287
+ } ) ;
288
+
289
+ suite ( 'here documents and here strings' , ( ) => {
290
+ test ( 'should split on here document' , ( ) => {
291
+ const commandLine = 'cat << EOF && echo done' ;
292
+ const expectedSubCommands = [ 'cat' , 'EOF' , 'echo done' ] ;
293
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
294
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
295
+ } ) ;
296
+
297
+ test ( 'should split on here string (bash/zsh)' , ( ) => {
298
+ const commandLine = 'grep pattern <<< "search this text"' ;
299
+ const expectedSubCommands = [ 'grep pattern' , '"search this text"' ] ;
300
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
301
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
302
+ } ) ;
303
+
304
+ test ( 'should split on numbered here string' , ( ) => {
305
+ const commandLine = 'command 3<<< "input data"' ;
306
+ const expectedSubCommands = [ 'command' , '"input data"' ] ;
307
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'zsh' , OperatingSystem . Linux ) ;
308
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
309
+ } ) ;
310
+ } ) ;
311
+
312
+ suite ( 'bidirectional redirection' , ( ) => {
313
+ test ( 'should split on read/write redirection' , ( ) => {
314
+ const commandLine = 'dialog <> /dev/tty1' ;
315
+ const expectedSubCommands = [ 'dialog' , '/dev/tty1' ] ;
316
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
317
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
318
+ } ) ;
319
+
320
+ test ( 'should split on numbered bidirectional redirection' , ( ) => {
321
+ const commandLine = 'program 3<> data.file' ;
322
+ const expectedSubCommands = [ 'program' , 'data.file' ] ;
323
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
324
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
325
+ } ) ;
326
+ } ) ;
327
+
328
+ suite ( 'PowerShell redirection' , ( ) => {
329
+ test ( 'should split on PowerShell output redirection' , ( ) => {
330
+ const commandLine = 'Get-Process > processes.txt' ;
331
+ const expectedSubCommands = [ 'Get-Process' , 'processes.txt' ] ;
332
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'powershell.exe' , OperatingSystem . Windows ) ;
333
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
334
+ } ) ;
335
+
336
+ test ( 'should split on PowerShell append redirection' , ( ) => {
337
+ const commandLine = 'Write-Output "log entry" >> log.txt' ;
338
+ const expectedSubCommands = [ 'Write-Output "log entry"' , 'log.txt' ] ;
339
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'pwsh.exe' , OperatingSystem . Windows ) ;
340
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
341
+ } ) ;
342
+
343
+ test ( 'should split on PowerShell error stream redirection' , ( ) => {
344
+ const commandLine = 'Get-Content nonexistent.txt 2> errors.log' ;
345
+ const expectedSubCommands = [ 'Get-Content nonexistent.txt' , 'errors.log' ] ;
346
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'powershell.exe' , OperatingSystem . Windows ) ;
347
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
348
+ } ) ;
349
+
350
+ test ( 'should split on PowerShell warning stream redirection' , ( ) => {
351
+ const commandLine = 'Get-Process 3> warnings.log' ;
352
+ const expectedSubCommands = [ 'Get-Process' , 'warnings.log' ] ;
353
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'pwsh.exe' , OperatingSystem . Windows ) ;
354
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
355
+ } ) ;
356
+
357
+ test ( 'should split on PowerShell verbose stream redirection' , ( ) => {
358
+ const commandLine = 'Get-ChildItem 4> verbose.log' ;
359
+ const expectedSubCommands = [ 'Get-ChildItem' , 'verbose.log' ] ;
360
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'powershell.exe' , OperatingSystem . Windows ) ;
361
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
362
+ } ) ;
363
+
364
+ test ( 'should split on PowerShell debug stream redirection' , ( ) => {
365
+ const commandLine = 'Invoke-Command 5> debug.log' ;
366
+ const expectedSubCommands = [ 'Invoke-Command' , 'debug.log' ] ;
367
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'pwsh.exe' , OperatingSystem . Windows ) ;
368
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
369
+ } ) ;
370
+
371
+ test ( 'should split on PowerShell information stream redirection' , ( ) => {
372
+ const commandLine = 'Write-Information "info" 6> info.log' ;
373
+ const expectedSubCommands = [ 'Write-Information "info"' , 'info.log' ] ;
374
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'powershell.exe' , OperatingSystem . Windows ) ;
375
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
376
+ } ) ;
377
+
378
+ test ( 'should split on PowerShell all streams redirection' , ( ) => {
379
+ const commandLine = 'Get-Process *> all_streams.log' ;
380
+ const expectedSubCommands = [ 'Get-Process' , 'all_streams.log' ] ;
381
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'pwsh.exe' , OperatingSystem . Windows ) ;
382
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
383
+ } ) ;
384
+
385
+ test ( 'should split on PowerShell stream to stream redirection' , ( ) => {
386
+ const commandLine = 'Write-Error "error" 2>&1' ;
387
+ const expectedSubCommands = [ 'Write-Error "error"' , '' ] ;
388
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'powershell.exe' , OperatingSystem . Windows ) ;
389
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
390
+ } ) ;
391
+ } ) ;
392
+
393
+ suite ( 'complex redirection scenarios' , ( ) => {
394
+ test ( 'should split on command with multiple redirections' , ( ) => {
395
+ const commandLine = 'command < input.txt > output.txt 2> errors.log' ;
396
+ const expectedSubCommands = [ 'command' , 'input.txt' , 'output.txt' , 'errors.log' ] ;
397
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
398
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
399
+ } ) ;
400
+
401
+ test ( 'should split on redirection with pipes and logical operators' , ( ) => {
402
+ const commandLine = 'cat file.txt | grep pattern > results.txt && echo "Found" || echo "Not found" 2> errors.log' ;
403
+ const expectedSubCommands = [ 'cat file.txt' , 'grep pattern' , 'results.txt' , 'echo "Found"' , 'echo "Not found"' , 'errors.log' ] ;
404
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
405
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
406
+ } ) ;
407
+
408
+ test ( 'should split on chained redirections' , ( ) => {
409
+ const commandLine = 'echo "step1" > temp.txt && cat temp.txt >> final.txt && rm temp.txt' ;
410
+ const expectedSubCommands = [ 'echo "step1"' , 'temp.txt' , 'cat temp.txt' , 'final.txt' , 'rm temp.txt' ] ;
411
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
412
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
413
+ } ) ;
414
+
415
+ test ( 'should handle redirection with background processes' , ( ) => {
416
+ const commandLine = 'long_running_command > output.log 2>&1 & echo "started"' ;
417
+ const expectedSubCommands = [ 'long_running_command' , 'output.log' , '' , 'echo "started"' ] ;
418
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'bash' , OperatingSystem . Linux ) ;
419
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
420
+ } ) ;
421
+ } ) ;
422
+
423
+ suite ( 'zsh-specific redirection' , ( ) => {
424
+ test ( 'should split on zsh noclobber override' , ( ) => {
425
+ const commandLine = 'echo "force" >! existing_file.txt' ;
426
+ const expectedSubCommands = [ 'echo "force"' , 'existing_file.txt' ] ;
427
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'zsh' , OperatingSystem . Linux ) ;
428
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
429
+ } ) ;
430
+
431
+ test ( 'should split on zsh clobber override' , ( ) => {
432
+ const commandLine = 'echo "overwrite" >| protected_file.txt' ;
433
+ const expectedSubCommands = [ 'echo "overwrite"' , 'protected_file.txt' ] ;
434
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'zsh' , OperatingSystem . Linux ) ;
435
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
436
+ } ) ;
437
+
438
+ test ( 'should split on zsh process substitution for input' , ( ) => {
439
+ const commandLine = 'diff <(sort file1.txt) <(sort file2.txt)' ;
440
+ const expectedSubCommands = [ 'diff' , 'sort file1.txt)' , 'sort file2.txt)' ] ;
441
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'zsh' , OperatingSystem . Linux ) ;
442
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
443
+ } ) ;
444
+
445
+ test ( 'should split on zsh multios' , ( ) => {
446
+ const commandLine = 'echo "test" | tee >(gzip > file1.gz) >(bzip2 > file1.bz2)' ;
447
+ const expectedSubCommands = [ 'echo "test"' , 'tee' , '(gzip' , 'file1.gz)' , '(bzip2' , 'file1.bz2)' ] ;
448
+ const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'zsh' , OperatingSystem . Linux ) ;
449
+ deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
450
+ } ) ;
451
+ } ) ;
452
+ } ) ;
453
+
191
454
suite ( 'complex command combinations' , ( ) => {
192
455
test ( 'should handle mixed operators in order' , ( ) => {
193
456
const commandLine = 'ls | grep test && echo found > result.txt || echo failed' ;
@@ -202,13 +465,6 @@ suite('splitCommandLineIntoSubCommands', () => {
202
465
const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'zsh' , OperatingSystem . Linux ) ;
203
466
deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
204
467
} ) ;
205
-
206
- test ( 'should handle here documents' , ( ) => {
207
- const commandLine = 'cat << EOF && echo done' ;
208
- const expectedSubCommands = [ 'cat' , 'EOF' , 'echo done' ] ;
209
- const actualSubCommands = splitCommandLineIntoSubCommands ( commandLine , 'sh' , OperatingSystem . Linux ) ;
210
- deepStrictEqual ( actualSubCommands , expectedSubCommands ) ;
211
- } ) ;
212
468
} ) ;
213
469
} ) ;
214
470
0 commit comments