linux-command 0.2.3__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {linux-command-0.2.3/linux_command.egg-info → linux_command-0.3.0}/PKG-INFO +43 -3
- linux-command-0.2.3/PKG-INFO → linux_command-0.3.0/README.md +32 -15
- {linux-command-0.2.3 → linux_command-0.3.0}/linux_command/linux_command.py +226 -115
- linux-command-0.2.3/README.md → linux_command-0.3.0/linux_command.egg-info/PKG-INFO +55 -1
- {linux-command-0.2.3 → linux_command-0.3.0}/linux_command.egg-info/SOURCES.txt +2 -1
- linux_command-0.3.0/tests/test_linux_command.py +308 -0
- {linux-command-0.2.3 → linux_command-0.3.0}/LICENSE +0 -0
- {linux-command-0.2.3 → linux_command-0.3.0}/linux_command/__init__.py +0 -0
- {linux-command-0.2.3 → linux_command-0.3.0}/linux_command.egg-info/dependency_links.txt +0 -0
- {linux-command-0.2.3 → linux_command-0.3.0}/linux_command.egg-info/entry_points.txt +0 -0
- {linux-command-0.2.3 → linux_command-0.3.0}/linux_command.egg-info/top_level.txt +0 -0
- {linux-command-0.2.3 → linux_command-0.3.0}/setup.cfg +0 -0
- {linux-command-0.2.3 → linux_command-0.3.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: linux-command
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A command line tool to perform custom tasks.
|
|
5
5
|
Home-page: https://github.com/MouxiaoHuang/linux-command
|
|
6
6
|
Author: Mouxiao Huang
|
|
@@ -11,6 +11,15 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Requires-Python: >=3.6
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: author-email
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
14
23
|
|
|
15
24
|
|
|
16
25
|
# linux-command
|
|
@@ -25,6 +34,16 @@ To install the package, run the following command:
|
|
|
25
34
|
pip install linux-command
|
|
26
35
|
```
|
|
27
36
|
|
|
37
|
+
## Install From Source (Development)
|
|
38
|
+
|
|
39
|
+
If you want to develop or modify the tool locally:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/MouxiaoHuang/linux-command.git
|
|
43
|
+
cd linux-command
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
28
47
|
## Usage
|
|
29
48
|
|
|
30
49
|
Once installed, you can access all commands using `cmd` followed by the specific command name. You can use `cmd -h` or `cmd --help` to see the supported commands. Below is a list of supported commands along with examples for each.
|
|
@@ -155,6 +174,12 @@ cmd ps-grep python
|
|
|
155
174
|
cmd kill process_name
|
|
156
175
|
```
|
|
157
176
|
|
|
177
|
+
To force kill:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
cmd kill --force process_name
|
|
181
|
+
```
|
|
182
|
+
|
|
158
183
|
---
|
|
159
184
|
|
|
160
185
|
### 21. `df` - Show disk usage in human-readable format
|
|
@@ -163,7 +188,7 @@ cmd kill process_name
|
|
|
163
188
|
cmd df
|
|
164
189
|
```
|
|
165
190
|
|
|
166
|
-
### 22. `du [path]` - Show disk usage for a specific file or directory
|
|
191
|
+
### 22. `du [path]` - Show disk usage for a specific file or directory (default: current directory)
|
|
167
192
|
|
|
168
193
|
```bash
|
|
169
194
|
cmd du /path/to/directory
|
|
@@ -256,6 +281,21 @@ This command will create a `.zip` file at `/path/to/output.zip` that contains ev
|
|
|
256
281
|
|
|
257
282
|
---
|
|
258
283
|
|
|
284
|
+
## Aliases
|
|
285
|
+
|
|
286
|
+
These commands are aliases for the same behavior:
|
|
287
|
+
|
|
288
|
+
- `lsf` = `ls-file`
|
|
289
|
+
- `lsd` = `ls-dir`
|
|
290
|
+
- `ls-bs` = `ls-block-size`
|
|
291
|
+
- `disk` = `du`
|
|
292
|
+
- `tar` = `tar-compress`
|
|
293
|
+
- `untar` = `tar-extract`
|
|
294
|
+
- `zip` = `zip-compress`
|
|
295
|
+
- `zip-all` = `zip-compress`
|
|
296
|
+
- `unzip` = `unzip-all`
|
|
297
|
+
- `convert-vid` = `convert-video`
|
|
298
|
+
|
|
259
299
|
## Contributing
|
|
260
300
|
|
|
261
301
|
We welcome contributions from the community! If you'd like to help improve `linux-command`, feel free to report issues or submit pull requests.
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: linux-command
|
|
3
|
-
Version: 0.2.3
|
|
4
|
-
Summary: A command line tool to perform custom tasks.
|
|
5
|
-
Home-page: https://github.com/MouxiaoHuang/linux-command
|
|
6
|
-
Author: Mouxiao Huang
|
|
7
|
-
Author-email: huangmouxiao@gmail.com
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.6
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
|
|
15
1
|
|
|
16
2
|
# linux-command
|
|
17
3
|
|
|
@@ -25,6 +11,16 @@ To install the package, run the following command:
|
|
|
25
11
|
pip install linux-command
|
|
26
12
|
```
|
|
27
13
|
|
|
14
|
+
## Install From Source (Development)
|
|
15
|
+
|
|
16
|
+
If you want to develop or modify the tool locally:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
git clone https://github.com/MouxiaoHuang/linux-command.git
|
|
20
|
+
cd linux-command
|
|
21
|
+
pip install -e .
|
|
22
|
+
```
|
|
23
|
+
|
|
28
24
|
## Usage
|
|
29
25
|
|
|
30
26
|
Once installed, you can access all commands using `cmd` followed by the specific command name. You can use `cmd -h` or `cmd --help` to see the supported commands. Below is a list of supported commands along with examples for each.
|
|
@@ -155,6 +151,12 @@ cmd ps-grep python
|
|
|
155
151
|
cmd kill process_name
|
|
156
152
|
```
|
|
157
153
|
|
|
154
|
+
To force kill:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
cmd kill --force process_name
|
|
158
|
+
```
|
|
159
|
+
|
|
158
160
|
---
|
|
159
161
|
|
|
160
162
|
### 21. `df` - Show disk usage in human-readable format
|
|
@@ -163,7 +165,7 @@ cmd kill process_name
|
|
|
163
165
|
cmd df
|
|
164
166
|
```
|
|
165
167
|
|
|
166
|
-
### 22. `du [path]` - Show disk usage for a specific file or directory
|
|
168
|
+
### 22. `du [path]` - Show disk usage for a specific file or directory (default: current directory)
|
|
167
169
|
|
|
168
170
|
```bash
|
|
169
171
|
cmd du /path/to/directory
|
|
@@ -256,6 +258,21 @@ This command will create a `.zip` file at `/path/to/output.zip` that contains ev
|
|
|
256
258
|
|
|
257
259
|
---
|
|
258
260
|
|
|
261
|
+
## Aliases
|
|
262
|
+
|
|
263
|
+
These commands are aliases for the same behavior:
|
|
264
|
+
|
|
265
|
+
- `lsf` = `ls-file`
|
|
266
|
+
- `lsd` = `ls-dir`
|
|
267
|
+
- `ls-bs` = `ls-block-size`
|
|
268
|
+
- `disk` = `du`
|
|
269
|
+
- `tar` = `tar-compress`
|
|
270
|
+
- `untar` = `tar-extract`
|
|
271
|
+
- `zip` = `zip-compress`
|
|
272
|
+
- `zip-all` = `zip-compress`
|
|
273
|
+
- `unzip` = `unzip-all`
|
|
274
|
+
- `convert-vid` = `convert-video`
|
|
275
|
+
|
|
259
276
|
## Contributing
|
|
260
277
|
|
|
261
278
|
We welcome contributions from the community! If you'd like to help improve `linux-command`, feel free to report issues or submit pull requests.
|
|
@@ -26,18 +26,23 @@ SOFTWARE.
|
|
|
26
26
|
For more information, visit the project page: https://github.com/MouxiaoHuang/linux-command
|
|
27
27
|
"""
|
|
28
28
|
import argparse
|
|
29
|
-
import os
|
|
30
29
|
import glob
|
|
30
|
+
import os
|
|
31
|
+
import shutil
|
|
32
|
+
import signal
|
|
33
|
+
import subprocess
|
|
34
|
+
from fnmatch import fnmatch
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
# Define the version
|
|
34
|
-
VERSION = "0.
|
|
38
|
+
VERSION = "0.3.0"
|
|
35
39
|
PROJECT_URL = "https://github.com/MouxiaoHuang/linux-command"
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
# Command descriptions
|
|
39
43
|
commands = {
|
|
40
44
|
'ls': 'List contents.',
|
|
45
|
+
'ls-all': 'List all files, including hidden ones.',
|
|
41
46
|
'lsf': 'Count all files or filter by a specified pattern, extension or keyword. Same as `ls-file`.',
|
|
42
47
|
'ls-file': 'Count all files or filter by a specified pattern, extension or keyword. Same as `lsf`.',
|
|
43
48
|
'lsd': 'Count all directories. Same as `ls-dir`.',
|
|
@@ -47,6 +52,7 @@ commands = {
|
|
|
47
52
|
'ls-human': 'List in human-readable format (for file sizes)',
|
|
48
53
|
'ls-long': 'Long format listing',
|
|
49
54
|
'ls-size': 'Sort files by size',
|
|
55
|
+
'ls-recursive': 'Recursively list files in directories and subdirectories.',
|
|
50
56
|
'ls-recursive-size': 'List all files and directories recursively, with sizes in human-readable format',
|
|
51
57
|
'ls-bs': 'Display the size of each file in specified block size (e.g., K, M, G).',
|
|
52
58
|
'ls-block-size': 'Display the size of each file in specified block size (e.g., K, M, G).',
|
|
@@ -64,12 +70,13 @@ commands = {
|
|
|
64
70
|
'rm': 'Remove file, directory, or multiple files by patterns (e.g., *.txt)',
|
|
65
71
|
'grep': 'Search for a pattern in files or output.',
|
|
66
72
|
'tar': 'Pack into .tar or .tar.gz file.',
|
|
67
|
-
'tar-
|
|
73
|
+
'tar-compress': 'Pack into .tar or .tar.gz file.',
|
|
68
74
|
'untar': 'Unpack .tar or .tar.gz file, or batch process in a directory.',
|
|
69
75
|
'tar-extract': 'Unpack .tar or .tar.gz file, or batch process in a directory.',
|
|
70
76
|
'tar-list': 'List all contents in a tar file.',
|
|
71
77
|
'tar-add': 'Add a file to a tar file.',
|
|
72
78
|
'zip': 'Pack a folder to a .zip file.',
|
|
79
|
+
'zip-compress': 'Pack a folder to a .zip file.',
|
|
73
80
|
'zip-all': 'Pack a folder to a .zip file.',
|
|
74
81
|
'unzip': 'Unpack all .zip files in a directory to another.',
|
|
75
82
|
'unzip-all': 'Unpack all .zip files in a directory to another.',
|
|
@@ -92,6 +99,130 @@ def confirm_action(message):
|
|
|
92
99
|
return confirmation in ['yes', 'y']
|
|
93
100
|
|
|
94
101
|
|
|
102
|
+
def run_cmd(cmd_args):
|
|
103
|
+
"""Run a command safely without invoking a shell."""
|
|
104
|
+
try:
|
|
105
|
+
subprocess.run(cmd_args, check=False)
|
|
106
|
+
except FileNotFoundError:
|
|
107
|
+
print(f"Command not found: {cmd_args[0]}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def parse_excludes(args):
|
|
111
|
+
excludes = []
|
|
112
|
+
i = 0
|
|
113
|
+
while i < len(args):
|
|
114
|
+
arg = args[i]
|
|
115
|
+
if arg == '--exclude':
|
|
116
|
+
if i + 1 < len(args):
|
|
117
|
+
excludes.append(args[i + 1])
|
|
118
|
+
i += 2
|
|
119
|
+
else:
|
|
120
|
+
break
|
|
121
|
+
elif arg.startswith('--exclude='):
|
|
122
|
+
excludes.append(arg.split('=', 1)[1])
|
|
123
|
+
i += 1
|
|
124
|
+
else:
|
|
125
|
+
excludes.append(arg)
|
|
126
|
+
i += 1
|
|
127
|
+
return excludes
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def expand_globs(args):
|
|
131
|
+
expanded = []
|
|
132
|
+
for arg in args:
|
|
133
|
+
if any(ch in arg for ch in ['*', '?', '[']):
|
|
134
|
+
matches = glob.glob(arg)
|
|
135
|
+
if matches:
|
|
136
|
+
expanded.extend(matches)
|
|
137
|
+
else:
|
|
138
|
+
expanded.append(arg)
|
|
139
|
+
else:
|
|
140
|
+
expanded.append(arg)
|
|
141
|
+
return expanded
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def list_files_in_dir(path):
|
|
145
|
+
try:
|
|
146
|
+
return os.listdir(path)
|
|
147
|
+
except FileNotFoundError:
|
|
148
|
+
print(f"Path not found: {path}")
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def count_dirs(root):
|
|
153
|
+
total = 0
|
|
154
|
+
for _, dirs, _ in os.walk(root):
|
|
155
|
+
total += len(dirs)
|
|
156
|
+
return total
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def count_files(root, pattern=None):
|
|
160
|
+
files = [f for f in list_files_in_dir(root) if os.path.isfile(os.path.join(root, f))]
|
|
161
|
+
if pattern is None:
|
|
162
|
+
return len(files)
|
|
163
|
+
if any(ch in pattern for ch in ['*', '?', '[']):
|
|
164
|
+
return sum(1 for f in files if fnmatch(f, pattern))
|
|
165
|
+
return sum(1 for f in files if pattern in f)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def filter_ps(keyword):
|
|
169
|
+
try:
|
|
170
|
+
result = subprocess.run(['ps', 'aux'], check=False, capture_output=True, text=True)
|
|
171
|
+
except FileNotFoundError:
|
|
172
|
+
print("Command not found: ps")
|
|
173
|
+
return []
|
|
174
|
+
lines = result.stdout.splitlines()
|
|
175
|
+
matches = []
|
|
176
|
+
for line in lines:
|
|
177
|
+
if keyword in line and 'ps aux' not in line:
|
|
178
|
+
matches.append(line)
|
|
179
|
+
return matches
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def kill_processes(target, force=False):
|
|
183
|
+
sig = signal.SIGKILL if force else signal.SIGTERM
|
|
184
|
+
if target.isdigit():
|
|
185
|
+
try:
|
|
186
|
+
os.kill(int(target), sig)
|
|
187
|
+
print(f"Killed PID {target} with signal {sig}.")
|
|
188
|
+
except ProcessLookupError:
|
|
189
|
+
print(f"No such process: {target}")
|
|
190
|
+
except PermissionError:
|
|
191
|
+
print(f"Permission denied to kill PID {target}")
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
matches = filter_ps(target)
|
|
195
|
+
if not matches:
|
|
196
|
+
print(f"No processes found matching: {target}")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
pids = []
|
|
200
|
+
for line in matches:
|
|
201
|
+
parts = line.split()
|
|
202
|
+
if len(parts) >= 2 and parts[1].isdigit():
|
|
203
|
+
pids.append(parts[1])
|
|
204
|
+
|
|
205
|
+
if not pids:
|
|
206
|
+
print(f"No processes found matching: {target}")
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
print("Matched processes:")
|
|
210
|
+
for line in matches:
|
|
211
|
+
print(line)
|
|
212
|
+
|
|
213
|
+
if confirm_action(f"Kill {len(pids)} process(es) matching '{target}'?"):
|
|
214
|
+
for pid in pids:
|
|
215
|
+
try:
|
|
216
|
+
os.kill(int(pid), sig)
|
|
217
|
+
except ProcessLookupError:
|
|
218
|
+
print(f"No such process: {pid}")
|
|
219
|
+
except PermissionError:
|
|
220
|
+
print(f"Permission denied to kill PID {pid}")
|
|
221
|
+
print("Kill completed.")
|
|
222
|
+
else:
|
|
223
|
+
print("Operation canceled.")
|
|
224
|
+
|
|
225
|
+
|
|
95
226
|
def main():
|
|
96
227
|
# Set up argparse
|
|
97
228
|
parser = argparse.ArgumentParser(
|
|
@@ -104,11 +235,17 @@ def main():
|
|
|
104
235
|
parser.add_argument('-V', '--version', action='store_true', help='Show program\'s version number and exit')
|
|
105
236
|
|
|
106
237
|
# Main command and subcommands
|
|
107
|
-
parser.
|
|
108
|
-
|
|
238
|
+
subparsers = parser.add_subparsers(dest='command')
|
|
239
|
+
for command, description in commands.items():
|
|
240
|
+
subparser = subparsers.add_parser(command, help=description, add_help=True)
|
|
241
|
+
subparser.add_argument('extra', nargs=argparse.REMAINDER, help='Additional arguments for the command')
|
|
109
242
|
|
|
110
243
|
# Parse the arguments
|
|
111
|
-
args = parser.
|
|
244
|
+
args, unknown = parser.parse_known_args()
|
|
245
|
+
if unknown:
|
|
246
|
+
if not hasattr(args, 'extra') or args.extra is None:
|
|
247
|
+
args.extra = []
|
|
248
|
+
args.extra.extend(unknown)
|
|
112
249
|
|
|
113
250
|
if args.help:
|
|
114
251
|
custom_help()
|
|
@@ -117,140 +254,107 @@ def main():
|
|
|
117
254
|
if args.version:
|
|
118
255
|
print(f'linux-command {VERSION}')
|
|
119
256
|
return
|
|
257
|
+
|
|
258
|
+
if args.command is None:
|
|
259
|
+
custom_help()
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
ls_simple = {
|
|
263
|
+
'ls': [],
|
|
264
|
+
'ls-all': ['-a'],
|
|
265
|
+
'ls-reverse': ['-r'],
|
|
266
|
+
'ls-time': ['-lt'],
|
|
267
|
+
'ls-long': ['-l'],
|
|
268
|
+
'ls-human': ['-lh'],
|
|
269
|
+
'ls-recursive': ['-R'],
|
|
270
|
+
'ls-recursive-size': ['-lRh'],
|
|
271
|
+
'ls-size': ['-lS'],
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
ps_simple = {
|
|
275
|
+
'ps': [],
|
|
276
|
+
'ps-all': ['-A'],
|
|
277
|
+
'ps-aux': ['aux'],
|
|
278
|
+
'ps-sort-memory': ['aux', '--sort=-%mem'],
|
|
279
|
+
'ps-sort-cpu': ['aux', '--sort=-%cpu'],
|
|
280
|
+
}
|
|
120
281
|
|
|
121
282
|
# `ls` commands
|
|
122
|
-
if args.command
|
|
123
|
-
|
|
124
|
-
# List current directory
|
|
125
|
-
os.system('ls')
|
|
126
|
-
else:
|
|
127
|
-
options = ' '.join(args.extra)
|
|
128
|
-
os.system(f'ls {options}')
|
|
129
|
-
|
|
130
|
-
# Advanced `ls` commands
|
|
283
|
+
if args.command in ls_simple:
|
|
284
|
+
run_cmd(['ls', *ls_simple[args.command], *expand_globs(args.extra)])
|
|
131
285
|
|
|
286
|
+
# Advanced `ls` commands
|
|
132
287
|
elif args.command == 'ls-dir' or args.command == 'lsd':
|
|
133
288
|
# Count the number of directories
|
|
134
|
-
|
|
289
|
+
print(count_dirs('.'))
|
|
135
290
|
|
|
136
291
|
elif args.command == 'ls-file' or args.command == 'lsf':
|
|
137
292
|
if len(args.extra) == 0:
|
|
138
293
|
# Count the number of all files
|
|
139
|
-
|
|
294
|
+
print(count_files('.'))
|
|
140
295
|
else:
|
|
141
296
|
# Count files of a specific type based on provided extension or pattern
|
|
142
297
|
pattern = args.extra[0] # First extra argument as file pattern
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
elif args.command == 'ls-reverse':
|
|
146
|
-
# List files and directories in reverse order
|
|
147
|
-
if len(args.extra) == 0:
|
|
148
|
-
os.system('ls -r')
|
|
149
|
-
else:
|
|
150
|
-
options = ' '.join(args.extra)
|
|
151
|
-
os.system(f'ls -r {options}')
|
|
152
|
-
|
|
153
|
-
elif args.command == 'ls-time':
|
|
154
|
-
# Sort by modification time, newest first
|
|
155
|
-
if len(args.extra) == 0:
|
|
156
|
-
os.system('ls -lt')
|
|
157
|
-
else:
|
|
158
|
-
options = ' '.join(args.extra)
|
|
159
|
-
os.system(f'ls -lt {options}')
|
|
160
|
-
|
|
161
|
-
elif args.command == 'ls-long':
|
|
162
|
-
# Long format listing
|
|
163
|
-
if len(args.extra) == 0:
|
|
164
|
-
os.system('ls -l')
|
|
165
|
-
else:
|
|
166
|
-
options = ' '.join(args.extra)
|
|
167
|
-
os.system(f'ls -l {options}')
|
|
168
|
-
|
|
169
|
-
elif args.command == 'ls-human':
|
|
170
|
-
# List in human-readable format (for file sizes)
|
|
171
|
-
if len(args.extra) == 0:
|
|
172
|
-
os.system('ls -lh')
|
|
173
|
-
else:
|
|
174
|
-
options = ' '.join(args.extra)
|
|
175
|
-
os.system(f'ls -lh {options}')
|
|
176
|
-
|
|
177
|
-
elif args.command == 'ls-recursive-size':
|
|
178
|
-
# List all files and directories recursively, with sizes in human-readable format
|
|
179
|
-
if len(args.extra) == 0:
|
|
180
|
-
os.system('ls -lRh')
|
|
181
|
-
else:
|
|
182
|
-
options = ' '.join(args.extra)
|
|
183
|
-
os.system(f'ls -lRh {options}')
|
|
184
|
-
|
|
185
|
-
elif args.command == 'ls-size':
|
|
186
|
-
# Sort files by size
|
|
187
|
-
if len(args.extra) == 0:
|
|
188
|
-
os.system('ls -lS')
|
|
189
|
-
else:
|
|
190
|
-
options = ' '.join(args.extra)
|
|
191
|
-
os.system(f'ls -lS {options}')
|
|
298
|
+
print(count_files('.', pattern=pattern))
|
|
192
299
|
|
|
193
300
|
elif args.command == 'ls-block-size' or args.command == 'ls-bs':
|
|
194
301
|
# Display the size of each file in specified block size
|
|
195
302
|
if len(args.extra) == 1:
|
|
196
303
|
block_size = args.extra[0]
|
|
197
|
-
|
|
304
|
+
run_cmd(['ls', f'--block-size={block_size}'])
|
|
198
305
|
else:
|
|
199
306
|
print('Please provide a valid block size (e.g., K, M, G).')
|
|
200
307
|
|
|
201
308
|
# `ps` commands
|
|
202
|
-
elif args.command
|
|
203
|
-
|
|
204
|
-
os.system('ps')
|
|
205
|
-
|
|
206
|
-
elif args.command == 'ps-all':
|
|
207
|
-
# Show all processes
|
|
208
|
-
os.system('ps -A')
|
|
309
|
+
elif args.command in ps_simple:
|
|
310
|
+
run_cmd(['ps', *ps_simple[args.command]])
|
|
209
311
|
|
|
210
312
|
elif args.command == 'ps-user':
|
|
211
313
|
# Show processes for a specific user
|
|
212
314
|
if len(args.extra) > 0:
|
|
213
315
|
user = args.extra[0]
|
|
214
|
-
|
|
316
|
+
run_cmd(['ps', '-u', user])
|
|
215
317
|
else:
|
|
216
318
|
print('Please provide a username to show processes for that user')
|
|
217
319
|
|
|
218
|
-
elif args.command == 'ps-aux':
|
|
219
|
-
# Show detailed information about all processes
|
|
220
|
-
os.system('ps aux')
|
|
221
|
-
|
|
222
|
-
elif args.command == 'ps-sort-memory':
|
|
223
|
-
# Sort processes by memory usage
|
|
224
|
-
os.system('ps aux --sort=-%mem')
|
|
225
|
-
|
|
226
|
-
elif args.command == 'ps-sort-cpu':
|
|
227
|
-
# Sort processes by CPU usage
|
|
228
|
-
os.system('ps aux --sort=-%cpu')
|
|
229
|
-
|
|
230
320
|
elif args.command == 'ps-grep':
|
|
231
321
|
# Search for a specific process by name or keyword
|
|
232
322
|
if len(args.extra) > 0:
|
|
233
323
|
keyword = args.extra[0]
|
|
234
|
-
|
|
324
|
+
matches = filter_ps(keyword)
|
|
325
|
+
for line in matches:
|
|
326
|
+
print(line)
|
|
235
327
|
else:
|
|
236
328
|
print('Please provide a keyword to search for in process list')
|
|
237
329
|
|
|
238
330
|
# `kill` command
|
|
239
331
|
elif args.command == 'kill':
|
|
240
|
-
if len(args.extra)
|
|
241
|
-
os.system(f'ps -ef | grep {args.extra[0]} | grep -v grep | cut -c 9-16 | xargs kill -9')
|
|
242
|
-
else:
|
|
332
|
+
if len(args.extra) == 0:
|
|
243
333
|
print('Please provide a process name or PID for kill command')
|
|
334
|
+
else:
|
|
335
|
+
force = False
|
|
336
|
+
extras = list(args.extra)
|
|
337
|
+
if '-9' in extras:
|
|
338
|
+
extras.remove('-9')
|
|
339
|
+
force = True
|
|
340
|
+
if '--force' in extras:
|
|
341
|
+
extras.remove('--force')
|
|
342
|
+
force = True
|
|
343
|
+
if len(extras) == 0:
|
|
344
|
+
print('Please provide a process name or PID for kill command')
|
|
345
|
+
return
|
|
346
|
+
target = extras[0]
|
|
347
|
+
kill_processes(target, force=force)
|
|
244
348
|
|
|
245
349
|
# Disk usage and free space commands
|
|
246
350
|
elif args.command == 'df':
|
|
247
|
-
|
|
351
|
+
run_cmd(['df', '-h'])
|
|
248
352
|
|
|
249
353
|
elif args.command == 'du' or args.command == 'disk':
|
|
250
354
|
if len(args.extra) > 0:
|
|
251
|
-
|
|
355
|
+
run_cmd(['du', '-sh', args.extra[0]])
|
|
252
356
|
else:
|
|
253
|
-
|
|
357
|
+
run_cmd(['du', '-sh', '.'])
|
|
254
358
|
|
|
255
359
|
# Remove files or directories with confirmation and support for bulk removal
|
|
256
360
|
elif args.command == 'rm':
|
|
@@ -258,9 +362,12 @@ def main():
|
|
|
258
362
|
target = args.extra[0]
|
|
259
363
|
if confirm_action(f"Are you sure you want to remove '{target}'?"):
|
|
260
364
|
if os.path.isdir(target):
|
|
261
|
-
|
|
365
|
+
shutil.rmtree(target)
|
|
366
|
+
elif os.path.isfile(target):
|
|
367
|
+
os.remove(target)
|
|
262
368
|
else:
|
|
263
|
-
|
|
369
|
+
print(f"Path not found: {target}")
|
|
370
|
+
return
|
|
264
371
|
print(f"'{target}' has been removed.")
|
|
265
372
|
else:
|
|
266
373
|
print(f"Operation canceled. '{target}' was not removed.")
|
|
@@ -273,7 +380,10 @@ def main():
|
|
|
273
380
|
print(f"Found {len(files_to_remove)} files to remove: {files_to_remove}")
|
|
274
381
|
if confirm_action(f"Are you sure you want to remove these files?"):
|
|
275
382
|
for file in files_to_remove:
|
|
276
|
-
os.
|
|
383
|
+
if os.path.isdir(file):
|
|
384
|
+
shutil.rmtree(file)
|
|
385
|
+
elif os.path.isfile(file):
|
|
386
|
+
os.remove(file)
|
|
277
387
|
print(f"Removed files matching {pattern}.")
|
|
278
388
|
else:
|
|
279
389
|
print(f"Operation canceled for files matching {pattern}.")
|
|
@@ -283,7 +393,7 @@ def main():
|
|
|
283
393
|
# Search for a pattern in files or output
|
|
284
394
|
elif args.command == 'grep':
|
|
285
395
|
if len(args.extra) == 2:
|
|
286
|
-
|
|
396
|
+
run_cmd(['grep', args.extra[0], args.extra[1]])
|
|
287
397
|
else:
|
|
288
398
|
print('Please provide a keyword and file path for grep command')
|
|
289
399
|
|
|
@@ -292,12 +402,11 @@ def main():
|
|
|
292
402
|
if len(args.extra) >= 2:
|
|
293
403
|
source = args.extra[0]
|
|
294
404
|
output = args.extra[1]
|
|
295
|
-
exclude = args.extra[2:] # Additional arguments as exclude patterns
|
|
296
|
-
exclude_params = ' '.join(f'--exclude={x}' for x in exclude)
|
|
405
|
+
exclude = parse_excludes(args.extra[2:]) # Additional arguments as exclude patterns
|
|
297
406
|
if output.endswith('.tar.gz'):
|
|
298
|
-
|
|
407
|
+
run_cmd(['tar', '-czvf', output, *[f'--exclude={x}' for x in exclude], source])
|
|
299
408
|
elif output.endswith('.tar'):
|
|
300
|
-
|
|
409
|
+
run_cmd(['tar', '-cvf', output, *[f'--exclude={x}' for x in exclude], source])
|
|
301
410
|
else:
|
|
302
411
|
print('Unsupported output format. Please provide .tar or .tar.gz as the output file extension.')
|
|
303
412
|
else:
|
|
@@ -319,14 +428,14 @@ def main():
|
|
|
319
428
|
|
|
320
429
|
for tar_file in tar_files:
|
|
321
430
|
if tar_file.endswith('.tar'):
|
|
322
|
-
|
|
431
|
+
run_cmd(['tar', '-xvf', tar_file, '-C', destination])
|
|
323
432
|
elif tar_file.endswith('.tar.gz'):
|
|
324
|
-
|
|
433
|
+
run_cmd(['tar', '-xzvf', tar_file, '-C', destination])
|
|
325
434
|
# Extract a single tar file
|
|
326
435
|
elif source.endswith('.tar.gz'):
|
|
327
|
-
|
|
436
|
+
run_cmd(['tar', '-xzvf', source, '-C', destination])
|
|
328
437
|
elif source.endswith('.tar'):
|
|
329
|
-
|
|
438
|
+
run_cmd(['tar', '-xvf', source, '-C', destination])
|
|
330
439
|
else:
|
|
331
440
|
print('Please provide a valid .tar or .tar.gz file, or a directory containing such files.')
|
|
332
441
|
|
|
@@ -334,13 +443,13 @@ def main():
|
|
|
334
443
|
|
|
335
444
|
elif args.command == 'tar-list':
|
|
336
445
|
if len(args.extra) > 0:
|
|
337
|
-
|
|
446
|
+
run_cmd(['tar', '-tvf', args.extra[0]])
|
|
338
447
|
else:
|
|
339
448
|
print('Please provide a tar file to list contents')
|
|
340
449
|
|
|
341
450
|
elif args.command == 'tar-add':
|
|
342
451
|
if len(args.extra) == 2:
|
|
343
|
-
|
|
452
|
+
run_cmd(['tar', '-rvf', args.extra[1], args.extra[0]])
|
|
344
453
|
else:
|
|
345
454
|
print('Please provide a file to add and the target tar file')
|
|
346
455
|
|
|
@@ -349,17 +458,19 @@ def main():
|
|
|
349
458
|
if len(args.extra) == 2:
|
|
350
459
|
source_dir = args.extra[0]
|
|
351
460
|
target_dir = args.extra[1]
|
|
352
|
-
|
|
461
|
+
for root, _, files in os.walk(source_dir):
|
|
462
|
+
for name in files:
|
|
463
|
+
if name.endswith('.zip'):
|
|
464
|
+
run_cmd(['unzip', os.path.join(root, name), '-d', target_dir])
|
|
353
465
|
else:
|
|
354
466
|
print('Please provide both a source and a target directory.')
|
|
355
467
|
|
|
356
468
|
# Zip compress
|
|
357
|
-
elif args.command == 'zip-all' or args.command == 'zip':
|
|
469
|
+
elif args.command == 'zip-all' or args.command == 'zip' or args.command == 'zip-compress':
|
|
358
470
|
if len(args.extra) >= 2:
|
|
359
471
|
output = args.extra[0]
|
|
360
|
-
sources = args.extra[1:] # All other arguments as sources to be zipped
|
|
361
|
-
|
|
362
|
-
os.system(f'zip -r {output} {source_params}')
|
|
472
|
+
sources = expand_globs(args.extra[1:]) # All other arguments as sources to be zipped
|
|
473
|
+
run_cmd(['zip', '-r', output, *sources])
|
|
363
474
|
else:
|
|
364
475
|
print('Please provide an output file name and at least one source to compress.')
|
|
365
476
|
|
|
@@ -378,10 +489,10 @@ def main():
|
|
|
378
489
|
for src_file in source_files:
|
|
379
490
|
basename = os.path.splitext(os.path.basename(src_file))[0]
|
|
380
491
|
dest_file = os.path.join(destination_dir, f'{basename}.{file_extension}')
|
|
381
|
-
|
|
492
|
+
run_cmd(['ffmpeg', '-i', src_file, '-c', 'copy', dest_file, '-y'])
|
|
382
493
|
else:
|
|
383
494
|
# Handle single file conversion
|
|
384
|
-
|
|
495
|
+
run_cmd(['ffmpeg', '-i', source, '-c', 'copy', destination, '-y'])
|
|
385
496
|
else:
|
|
386
497
|
print('Usage: convert-video [source file or pattern] [destination file or pattern]')
|
|
387
498
|
|
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: linux-command
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A command line tool to perform custom tasks.
|
|
5
|
+
Home-page: https://github.com/MouxiaoHuang/linux-command
|
|
6
|
+
Author: Mouxiao Huang
|
|
7
|
+
Author-email: huangmouxiao@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: author-email
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
1
24
|
|
|
2
25
|
# linux-command
|
|
3
26
|
|
|
@@ -11,6 +34,16 @@ To install the package, run the following command:
|
|
|
11
34
|
pip install linux-command
|
|
12
35
|
```
|
|
13
36
|
|
|
37
|
+
## Install From Source (Development)
|
|
38
|
+
|
|
39
|
+
If you want to develop or modify the tool locally:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/MouxiaoHuang/linux-command.git
|
|
43
|
+
cd linux-command
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
14
47
|
## Usage
|
|
15
48
|
|
|
16
49
|
Once installed, you can access all commands using `cmd` followed by the specific command name. You can use `cmd -h` or `cmd --help` to see the supported commands. Below is a list of supported commands along with examples for each.
|
|
@@ -141,6 +174,12 @@ cmd ps-grep python
|
|
|
141
174
|
cmd kill process_name
|
|
142
175
|
```
|
|
143
176
|
|
|
177
|
+
To force kill:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
cmd kill --force process_name
|
|
181
|
+
```
|
|
182
|
+
|
|
144
183
|
---
|
|
145
184
|
|
|
146
185
|
### 21. `df` - Show disk usage in human-readable format
|
|
@@ -149,7 +188,7 @@ cmd kill process_name
|
|
|
149
188
|
cmd df
|
|
150
189
|
```
|
|
151
190
|
|
|
152
|
-
### 22. `du [path]` - Show disk usage for a specific file or directory
|
|
191
|
+
### 22. `du [path]` - Show disk usage for a specific file or directory (default: current directory)
|
|
153
192
|
|
|
154
193
|
```bash
|
|
155
194
|
cmd du /path/to/directory
|
|
@@ -242,6 +281,21 @@ This command will create a `.zip` file at `/path/to/output.zip` that contains ev
|
|
|
242
281
|
|
|
243
282
|
---
|
|
244
283
|
|
|
284
|
+
## Aliases
|
|
285
|
+
|
|
286
|
+
These commands are aliases for the same behavior:
|
|
287
|
+
|
|
288
|
+
- `lsf` = `ls-file`
|
|
289
|
+
- `lsd` = `ls-dir`
|
|
290
|
+
- `ls-bs` = `ls-block-size`
|
|
291
|
+
- `disk` = `du`
|
|
292
|
+
- `tar` = `tar-compress`
|
|
293
|
+
- `untar` = `tar-extract`
|
|
294
|
+
- `zip` = `zip-compress`
|
|
295
|
+
- `zip-all` = `zip-compress`
|
|
296
|
+
- `unzip` = `unzip-all`
|
|
297
|
+
- `convert-vid` = `convert-video`
|
|
298
|
+
|
|
245
299
|
## Contributing
|
|
246
300
|
|
|
247
301
|
We welcome contributions from the community! If you'd like to help improve `linux-command`, feel free to report issues or submit pull requests.
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import signal
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
7
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
8
|
+
|
|
9
|
+
import linux_command.linux_command as lc
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RunSpy:
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.calls = []
|
|
15
|
+
|
|
16
|
+
def __call__(self, cmd_args, check=False, **kwargs):
|
|
17
|
+
# Mimic subprocess.run signature used in code.
|
|
18
|
+
self.calls.append(list(cmd_args))
|
|
19
|
+
class Result:
|
|
20
|
+
stdout = ""
|
|
21
|
+
return Result()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RunCmdSpy:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.calls = []
|
|
27
|
+
|
|
28
|
+
def __call__(self, cmd_args):
|
|
29
|
+
self.calls.append(list(cmd_args))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_main_with_args(monkeypatch, argv, run_spy=None):
|
|
33
|
+
monkeypatch.setattr(sys, "argv", ["cmd", *argv])
|
|
34
|
+
if run_spy is not None:
|
|
35
|
+
monkeypatch.setattr(lc.subprocess, "run", run_spy)
|
|
36
|
+
lc.main()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_ls_and_ls_all(monkeypatch):
|
|
40
|
+
run_spy = RunSpy()
|
|
41
|
+
run_main_with_args(monkeypatch, ["ls"], run_spy)
|
|
42
|
+
run_main_with_args(monkeypatch, ["ls-all"], run_spy)
|
|
43
|
+
assert run_spy.calls[0] == ["ls"]
|
|
44
|
+
assert run_spy.calls[1] == ["ls", "-a"]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_ls_variants(monkeypatch):
|
|
48
|
+
run_cmd_spy = RunCmdSpy()
|
|
49
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
50
|
+
for cmd, expected in [
|
|
51
|
+
("ls-reverse", ["ls", "-r"]),
|
|
52
|
+
("ls-time", ["ls", "-lt"]),
|
|
53
|
+
("ls-long", ["ls", "-l"]),
|
|
54
|
+
("ls-human", ["ls", "-lh"]),
|
|
55
|
+
("ls-recursive", ["ls", "-R"]),
|
|
56
|
+
("ls-recursive-size", ["ls", "-lRh"]),
|
|
57
|
+
("ls-size", ["ls", "-lS"]),
|
|
58
|
+
]:
|
|
59
|
+
run_main_with_args(monkeypatch, [cmd])
|
|
60
|
+
assert run_cmd_spy.calls[-1] == expected
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_ls_block_size_ok(monkeypatch):
|
|
64
|
+
run_cmd_spy = RunCmdSpy()
|
|
65
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
66
|
+
run_main_with_args(monkeypatch, ["ls-block-size", "M"])
|
|
67
|
+
assert run_cmd_spy.calls[0] == ["ls", "--block-size=M"]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_ls_block_size_missing_arg(monkeypatch, capsys):
|
|
71
|
+
run_spy = RunSpy()
|
|
72
|
+
run_main_with_args(monkeypatch, ["ls-block-size"], run_spy)
|
|
73
|
+
out = capsys.readouterr().out
|
|
74
|
+
assert "Please provide a valid block size" in out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_ps_user(monkeypatch):
|
|
78
|
+
run_spy = RunSpy()
|
|
79
|
+
run_main_with_args(monkeypatch, ["ps-user", "root"], run_spy)
|
|
80
|
+
assert run_spy.calls[0] == ["ps", "-u", "root"]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_ps_variants(monkeypatch):
|
|
84
|
+
run_cmd_spy = RunCmdSpy()
|
|
85
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
86
|
+
for cmd, expected in [
|
|
87
|
+
("ps", ["ps"]),
|
|
88
|
+
("ps-all", ["ps", "-A"]),
|
|
89
|
+
("ps-aux", ["ps", "aux"]),
|
|
90
|
+
("ps-sort-memory", ["ps", "aux", "--sort=-%mem"]),
|
|
91
|
+
("ps-sort-cpu", ["ps", "aux", "--sort=-%cpu"]),
|
|
92
|
+
]:
|
|
93
|
+
run_main_with_args(monkeypatch, [cmd])
|
|
94
|
+
assert run_cmd_spy.calls[-1] == expected
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_ps_grep_filters(monkeypatch, capsys):
|
|
98
|
+
def fake_run(cmd_args, check=False, capture_output=False, text=False):
|
|
99
|
+
class Result:
|
|
100
|
+
stdout = "root 1 0.0 0.1 ? Ss 00:00 python\nuser 2 0.0 0.1 ? Ss 00:00 bash\n"
|
|
101
|
+
return Result()
|
|
102
|
+
|
|
103
|
+
monkeypatch.setattr(lc.subprocess, "run", fake_run)
|
|
104
|
+
run_main_with_args(monkeypatch, ["ps-grep", "python"])
|
|
105
|
+
out = capsys.readouterr().out
|
|
106
|
+
assert "python" in out
|
|
107
|
+
assert "bash" not in out
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_kill_pid(monkeypatch, capsys):
|
|
111
|
+
killed = []
|
|
112
|
+
|
|
113
|
+
def fake_kill(pid, sig):
|
|
114
|
+
killed.append((pid, sig))
|
|
115
|
+
|
|
116
|
+
monkeypatch.setattr(lc.os, "kill", fake_kill)
|
|
117
|
+
run_main_with_args(monkeypatch, ["kill", "1234"])
|
|
118
|
+
assert killed == [(1234, signal.SIGTERM)]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_kill_force_pid(monkeypatch):
|
|
122
|
+
killed = []
|
|
123
|
+
|
|
124
|
+
def fake_kill(pid, sig):
|
|
125
|
+
killed.append((pid, sig))
|
|
126
|
+
|
|
127
|
+
monkeypatch.setattr(lc.os, "kill", fake_kill)
|
|
128
|
+
run_main_with_args(monkeypatch, ["kill", "--force", "999"])
|
|
129
|
+
assert killed == [(999, signal.SIGKILL)]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_kill_name_confirm(monkeypatch, capsys):
|
|
133
|
+
def fake_run(cmd_args, check=False, capture_output=False, text=False):
|
|
134
|
+
class Result:
|
|
135
|
+
stdout = "root 10 0.0 0.1 ? Ss 00:00 myproc\n"
|
|
136
|
+
return Result()
|
|
137
|
+
|
|
138
|
+
killed = []
|
|
139
|
+
|
|
140
|
+
def fake_kill(pid, sig):
|
|
141
|
+
killed.append((pid, sig))
|
|
142
|
+
|
|
143
|
+
monkeypatch.setattr(lc.subprocess, "run", fake_run)
|
|
144
|
+
monkeypatch.setattr(lc.os, "kill", fake_kill)
|
|
145
|
+
monkeypatch.setattr(builtins, "input", lambda _: "y")
|
|
146
|
+
run_main_with_args(monkeypatch, ["kill", "myproc"])
|
|
147
|
+
assert killed == [(10, signal.SIGTERM)]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_du_default(monkeypatch):
|
|
151
|
+
run_spy = RunSpy()
|
|
152
|
+
run_main_with_args(monkeypatch, ["du"], run_spy)
|
|
153
|
+
assert run_spy.calls[0] == ["du", "-sh", "."]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_disk_alias(monkeypatch):
|
|
157
|
+
run_cmd_spy = RunCmdSpy()
|
|
158
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
159
|
+
run_main_with_args(monkeypatch, ["disk", "/tmp"])
|
|
160
|
+
assert run_cmd_spy.calls[0] == ["du", "-sh", "/tmp"]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_rm_single_file(monkeypatch, tmp_path):
|
|
164
|
+
target = tmp_path / "a.txt"
|
|
165
|
+
target.write_text("x")
|
|
166
|
+
monkeypatch.setattr(builtins, "input", lambda _: "y")
|
|
167
|
+
run_main_with_args(monkeypatch, ["rm", str(target)])
|
|
168
|
+
assert not target.exists()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_rm_pattern(monkeypatch, tmp_path):
|
|
172
|
+
(tmp_path / "a.log").write_text("x")
|
|
173
|
+
(tmp_path / "b.log").write_text("x")
|
|
174
|
+
(tmp_path / "c.txt").write_text("x")
|
|
175
|
+
monkeypatch.setattr(builtins, "input", lambda _: "y")
|
|
176
|
+
run_main_with_args(monkeypatch, ["rm", str(tmp_path), "*.log"])
|
|
177
|
+
assert not (tmp_path / "a.log").exists()
|
|
178
|
+
assert not (tmp_path / "b.log").exists()
|
|
179
|
+
assert (tmp_path / "c.txt").exists()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_expand_globs(tmp_path):
|
|
183
|
+
(tmp_path / "x.txt").write_text("x")
|
|
184
|
+
(tmp_path / "y.txt").write_text("x")
|
|
185
|
+
args = [str(tmp_path / "*.txt")]
|
|
186
|
+
expanded = lc.expand_globs(args)
|
|
187
|
+
assert len(expanded) == 2
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_ls_dir_and_ls_file(monkeypatch, tmp_path, capsys):
|
|
191
|
+
(tmp_path / "a.txt").write_text("x")
|
|
192
|
+
(tmp_path / "b.log").write_text("x")
|
|
193
|
+
(tmp_path / "dir1").mkdir()
|
|
194
|
+
(tmp_path / "dir2").mkdir()
|
|
195
|
+
monkeypatch.chdir(tmp_path)
|
|
196
|
+
|
|
197
|
+
run_main_with_args(monkeypatch, ["ls-dir"])
|
|
198
|
+
out = capsys.readouterr().out.strip()
|
|
199
|
+
assert out == "2"
|
|
200
|
+
|
|
201
|
+
run_main_with_args(monkeypatch, ["ls-file"])
|
|
202
|
+
out = capsys.readouterr().out.strip()
|
|
203
|
+
assert out == "2"
|
|
204
|
+
|
|
205
|
+
run_main_with_args(monkeypatch, ["lsf", "*.log"])
|
|
206
|
+
out = capsys.readouterr().out.strip()
|
|
207
|
+
assert out == "1"
|
|
208
|
+
|
|
209
|
+
run_main_with_args(monkeypatch, ["lsd"])
|
|
210
|
+
out = capsys.readouterr().out.strip()
|
|
211
|
+
assert out == "2"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_grep_command(monkeypatch):
|
|
215
|
+
run_cmd_spy = RunCmdSpy()
|
|
216
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
217
|
+
run_main_with_args(monkeypatch, ["grep", "needle", "file.txt"])
|
|
218
|
+
assert run_cmd_spy.calls[0] == ["grep", "needle", "file.txt"]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_tar_compress(monkeypatch):
|
|
222
|
+
run_cmd_spy = RunCmdSpy()
|
|
223
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
224
|
+
run_main_with_args(monkeypatch, ["tar-compress", "src", "out.tar"])
|
|
225
|
+
run_main_with_args(monkeypatch, ["tar", "src", "out.tar.gz", "--exclude", "node_modules"])
|
|
226
|
+
assert run_cmd_spy.calls[0] == ["tar", "-cvf", "out.tar", "src"]
|
|
227
|
+
assert run_cmd_spy.calls[1] == ["tar", "-czvf", "out.tar.gz", "--exclude=node_modules", "src"]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_tar_compress_bad_ext(monkeypatch, capsys):
|
|
231
|
+
run_cmd_spy = RunCmdSpy()
|
|
232
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
233
|
+
run_main_with_args(monkeypatch, ["tar-compress", "src", "out.zip"])
|
|
234
|
+
out = capsys.readouterr().out
|
|
235
|
+
assert "Unsupported output format" in out
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def test_tar_extract_single(monkeypatch):
|
|
239
|
+
run_cmd_spy = RunCmdSpy()
|
|
240
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
241
|
+
run_main_with_args(monkeypatch, ["tar-extract", "a.tar.gz", "dest"])
|
|
242
|
+
run_main_with_args(monkeypatch, ["untar", "b.tar", "dest"])
|
|
243
|
+
assert run_cmd_spy.calls[0] == ["tar", "-xzvf", "a.tar.gz", "-C", "dest"]
|
|
244
|
+
assert run_cmd_spy.calls[1] == ["tar", "-xvf", "b.tar", "-C", "dest"]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_tar_extract_dir(monkeypatch, tmp_path):
|
|
248
|
+
src = tmp_path / "src"
|
|
249
|
+
src.mkdir()
|
|
250
|
+
(src / "a.tar").write_text("x")
|
|
251
|
+
(src / "b.tar.gz").write_text("x")
|
|
252
|
+
run_cmd_spy = RunCmdSpy()
|
|
253
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
254
|
+
run_main_with_args(monkeypatch, ["tar-extract", str(src), str(tmp_path / "dest"), "all"])
|
|
255
|
+
assert run_cmd_spy.calls[0] == ["tar", "-xvf", str(src / "a.tar"), "-C", str(tmp_path / "dest")]
|
|
256
|
+
assert run_cmd_spy.calls[1] == ["tar", "-xzvf", str(src / "b.tar.gz"), "-C", str(tmp_path / "dest")]
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_tar_list_and_add(monkeypatch):
|
|
260
|
+
run_cmd_spy = RunCmdSpy()
|
|
261
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
262
|
+
run_main_with_args(monkeypatch, ["tar-list", "a.tar"])
|
|
263
|
+
run_main_with_args(monkeypatch, ["tar-add", "file.txt", "a.tar"])
|
|
264
|
+
assert run_cmd_spy.calls[0] == ["tar", "-tvf", "a.tar"]
|
|
265
|
+
assert run_cmd_spy.calls[1] == ["tar", "-rvf", "a.tar", "file.txt"]
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def test_unzip_all(monkeypatch, tmp_path):
|
|
269
|
+
src = tmp_path / "zips"
|
|
270
|
+
src.mkdir()
|
|
271
|
+
(src / "a.zip").write_text("x")
|
|
272
|
+
(src / "b.txt").write_text("x")
|
|
273
|
+
run_cmd_spy = RunCmdSpy()
|
|
274
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
275
|
+
run_main_with_args(monkeypatch, ["unzip-all", str(src), str(tmp_path / "out")])
|
|
276
|
+
assert run_cmd_spy.calls[0] == ["unzip", str(src / "a.zip"), "-d", str(tmp_path / "out")]
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_zip_commands(monkeypatch, tmp_path):
|
|
280
|
+
(tmp_path / "a.txt").write_text("x")
|
|
281
|
+
(tmp_path / "b.txt").write_text("x")
|
|
282
|
+
run_cmd_spy = RunCmdSpy()
|
|
283
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
284
|
+
run_main_with_args(monkeypatch, ["zip", "out.zip", str(tmp_path / "*.txt")])
|
|
285
|
+
run_main_with_args(monkeypatch, ["zip-compress", "out.zip", str(tmp_path / "a.txt")])
|
|
286
|
+
assert run_cmd_spy.calls[0][0:3] == ["zip", "-r", "out.zip"]
|
|
287
|
+
assert run_cmd_spy.calls[1] == ["zip", "-r", "out.zip", str(tmp_path / "a.txt")]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_convert_video_single(monkeypatch):
|
|
291
|
+
run_cmd_spy = RunCmdSpy()
|
|
292
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
293
|
+
run_main_with_args(monkeypatch, ["convert-video", "a.mp4", "b.mkv"])
|
|
294
|
+
assert run_cmd_spy.calls[0] == ["ffmpeg", "-i", "a.mp4", "-c", "copy", "b.mkv", "-y"]
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_convert_video_wildcard(monkeypatch, tmp_path):
|
|
298
|
+
src_dir = tmp_path / "src"
|
|
299
|
+
out_dir = tmp_path / "out"
|
|
300
|
+
src_dir.mkdir()
|
|
301
|
+
out_dir.mkdir()
|
|
302
|
+
(src_dir / "a.mp4").write_text("x")
|
|
303
|
+
(src_dir / "b.mp4").write_text("x")
|
|
304
|
+
run_cmd_spy = RunCmdSpy()
|
|
305
|
+
monkeypatch.setattr(lc, "run_cmd", run_cmd_spy)
|
|
306
|
+
run_main_with_args(monkeypatch, ["convert-vid", str(src_dir / "*.mp4"), str(out_dir / "out.mkv")])
|
|
307
|
+
assert run_cmd_spy.calls[0] == ["ffmpeg", "-i", str(src_dir / "a.mp4"), "-c", "copy", str(out_dir / "a.mkv"), "-y"]
|
|
308
|
+
assert run_cmd_spy.calls[1] == ["ffmpeg", "-i", str(src_dir / "b.mp4"), "-c", "copy", str(out_dir / "b.mkv"), "-y"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|