|
1 | | -def split_bash_commands(commands): |
2 | | - # States |
3 | | - NORMAL = 0 |
4 | | - IN_SINGLE_QUOTE = 1 |
5 | | - IN_DOUBLE_QUOTE = 2 |
6 | | - IN_HEREDOC = 3 |
7 | | - |
8 | | - state = NORMAL |
9 | | - heredoc_trigger = None |
10 | | - result = [] |
11 | | - current_command: list[str] = [] |
12 | | - |
13 | | - i = 0 |
14 | | - while i < len(commands): |
15 | | - char = commands[i] |
16 | | - |
17 | | - if state == NORMAL: |
18 | | - if char == "'": |
19 | | - state = IN_SINGLE_QUOTE |
20 | | - elif char == '"': |
21 | | - state = IN_DOUBLE_QUOTE |
22 | | - elif char == '\\': |
23 | | - # Check if this is escaping a newline |
24 | | - if i + 1 < len(commands) and commands[i + 1] == '\n': |
25 | | - i += 1 # Skip the newline |
26 | | - # Continue with the next line as part of the same command |
27 | | - i += 1 # Move to the first character of the next line |
28 | | - continue |
29 | | - elif char == '\n': |
30 | | - if not heredoc_trigger and current_command: |
31 | | - result.append(''.join(current_command).strip()) |
32 | | - current_command = [] |
33 | | - elif char == '<' and commands[i : i + 2] == '<<': |
34 | | - # Detect heredoc |
35 | | - state = IN_HEREDOC |
36 | | - i += 2 # Skip '<<' |
37 | | - while commands[i] == ' ': |
38 | | - i += 1 |
39 | | - start = i |
40 | | - while commands[i] not in [' ', '\n']: |
41 | | - i += 1 |
42 | | - heredoc_trigger = commands[start:i] |
43 | | - current_command.append(commands[start - 2 : i]) # Include '<<' |
44 | | - continue # Skip incrementing i at the end of the loop |
45 | | - current_command.append(char) |
46 | | - |
47 | | - elif state == IN_SINGLE_QUOTE: |
48 | | - current_command.append(char) |
49 | | - if char == "'" and commands[i - 1] != '\\': |
50 | | - state = NORMAL |
| 1 | +import bashlex |
51 | 2 |
|
52 | | - elif state == IN_DOUBLE_QUOTE: |
53 | | - current_command.append(char) |
54 | | - if char == '"' and commands[i - 1] != '\\': |
55 | | - state = NORMAL |
| 3 | +from opendevin.core.logger import opendevin_logger as logger |
56 | 4 |
|
57 | | - elif state == IN_HEREDOC: |
58 | | - current_command.append(char) |
59 | | - if ( |
60 | | - char == '\n' |
61 | | - and heredoc_trigger |
62 | | - and commands[i + 1 : i + 1 + len(heredoc_trigger) + 1] |
63 | | - == heredoc_trigger + '\n' |
64 | | - ): |
65 | | - # Check if the next line starts with the heredoc trigger followed by a newline |
66 | | - i += ( |
67 | | - len(heredoc_trigger) + 1 |
68 | | - ) # Move past the heredoc trigger and newline |
69 | | - current_command.append( |
70 | | - heredoc_trigger + '\n' |
71 | | - ) # Include the heredoc trigger and newline |
72 | | - result.append(''.join(current_command).strip()) |
73 | | - current_command = [] |
74 | | - heredoc_trigger = None |
75 | | - state = NORMAL |
76 | | - continue |
77 | | - |
78 | | - i += 1 |
79 | | - |
80 | | - # Add the last command if any |
81 | | - if current_command: |
82 | | - result.append(''.join(current_command).strip()) |
83 | | - |
84 | | - # Remove any empty strings from the result |
85 | | - result = [cmd for cmd in result if cmd] |
86 | 5 |
|
| 6 | +def split_bash_commands(commands): |
| 7 | + try: |
| 8 | + parsed = bashlex.parse(commands) |
| 9 | + except bashlex.errors.ParsingError as e: |
| 10 | + logger.error( |
| 11 | + f'Failed to parse bash commands\n[input]: {commands}\n[error]: {e}' |
| 12 | + ) |
| 13 | + # If parsing fails, return the original commands |
| 14 | + return [commands] |
| 15 | + |
| 16 | + result: list[str] = [] |
| 17 | + last_end = 0 |
| 18 | + |
| 19 | + for node in parsed: |
| 20 | + start, end = node.pos |
| 21 | + |
| 22 | + # Include any text between the last command and this one |
| 23 | + if start > last_end: |
| 24 | + between = commands[last_end:start] |
| 25 | + logger.debug(f'BASH PARSING between: {between}') |
| 26 | + if result: |
| 27 | + result[-1] += between.rstrip() |
| 28 | + elif between.strip(): |
| 29 | + # THIS SHOULD NOT HAPPEN |
| 30 | + result.append(between.rstrip()) |
| 31 | + |
| 32 | + # Extract the command, preserving original formatting |
| 33 | + command = commands[start:end].rstrip() |
| 34 | + logger.debug(f'BASH PARSING command: {command}') |
| 35 | + result.append(command) |
| 36 | + |
| 37 | + last_end = end |
| 38 | + |
| 39 | + # Add any remaining text after the last command to the last command |
| 40 | + remaining = commands[last_end:].rstrip() |
| 41 | + logger.debug(f'BASH PARSING remaining: {remaining}') |
| 42 | + if last_end < len(commands) and result: |
| 43 | + result[-1] += remaining |
| 44 | + logger.debug(f'BASH PARSING result[-1] += remaining: {result[-1]}') |
| 45 | + elif last_end < len(commands): |
| 46 | + if remaining: |
| 47 | + result.append(remaining) |
| 48 | + logger.debug(f'BASH PARSING result.append(remaining): {result[-1]}') |
87 | 49 | return result |
0 commit comments