Công cụ chuyển đổi Docker run sang Docker Compose là gì?
Công cụ chuyển đổi Docker run sang Docker Compose là công cụ nhận một lệnh docker run dạng văn bản và tạo YAML mô tả một service trong Docker Compose. Đây là phép chuyển cách biểu diễn cấu hình: từ các tùy chọn nối tiếp trên dòng lệnh sang những trường như image, ports, volumes và environment. Công cụ không tự chạy container hay triển khai ứng dụng.
Nhu cầu mà công cụ hỗ trợ là làm một lệnh dài dễ đọc, dễ đối chiếu và dễ chỉnh sửa hơn. Khi thông tin về cổng, lưu trữ và môi trường được tách thành từng nhóm, người dùng có thể rà soát cấu hình mà không phải tìm từng tùy chọn giữa một chuỗi lệnh. Kết quả là văn bản YAML có thể sao chép để tiếp tục sử dụng.
Theo mã được cung cấp, công cụ chỉ nhận diện một tập tùy chọn nhất định. Vì vậy, không nên hiểu “chuyển đổi” là bảo đảm mọi lệnh Docker đều được giữ nguyên hành vi. Một số tùy chọn bị bỏ qua; một số cách viết có thể khiến image hoặc lệnh thực thi bị nhận nhầm.
Tạo cấu hình, xác thực cấu hình và triển khai là ba việc khác nhau. Công cụ thực hiện việc đầu tiên. Trạng thái thành công cho biết quá trình phân tích và sinh văn bản không phát sinh lỗi, chứ không chứng minh Docker Compose chấp nhận cấu hình hoặc container hoạt động đúng trong môi trường của bạn.
Các thuật ngữ cần hiểu khi đọc cấu hình Docker Compose
Các khái niệm dưới đây là kiến thức nền về Docker, giúp bạn đọc kết quả. Chúng không có nghĩa công cụ tự kiểm tra image, quản lý lưu trữ hay xác minh kết nối mạng.
Image và container
Image là mẫu đóng gói dùng để tạo container, thường chứa chương trình cùng các thành phần cần thiết. Container là một phiên bản thực thi được tạo từ image đó. Trong kết quả chuyển đổi, image chỉ mẫu được sử dụng, còn container_name là tên container nếu lệnh có khai báo. Hai thông tin không thay thế nhau: tên container dễ nhớ không chứng minh image tồn tại, và công cụ không kiểm tra khả năng tải image.
Service trong Docker Compose
Service là đơn vị khai báo cấu hình ứng dụng trong Compose, bao gồm image và các thiết lập liên quan. Công cụ tạo một service cho mỗi lần chuyển đổi. Tên service được lấy từ tên container sau khi lọc ký tự, hoặc dùng app khi không có tên container. Đừng nhầm tên service với tên image hay tên container: chúng có vai trò khác nhau, dù đôi khi được đặt giống nhau để dễ quản lý.
Ánh xạ cổng
Ánh xạ cổng đưa một cổng trong container ra phía máy chủ. Dạng thường gặp là cổng máy chủ:cổng container; chẳng hạn 8080:80 biểu thị cổng 8080 phía máy chủ và cổng 80 phía container. Đảo thứ tự sẽ thay đổi cấu hình mong muốn. Công cụ chép giá trị này vào ports, nhưng không kiểm tra cổng đang bị chiếm, dịch vụ có lắng nghe hay tường lửa có cho phép truy cập hay không.
Volume và bind mount
Named volume là vùng lưu trữ được Docker quản lý theo tên, còn bind mount gắn một đường dẫn của máy chủ vào container. Cả hai có thể xuất hiện trong tùy chọn -v, nhưng không phải cùng một cơ chế. Công cụ đưa chuỗi nhận được vào volumes mà không phân loại. Đặc biệt, named volume không được tự bổ sung khai báo ở cấp cao nhất, nên việc thấy nó trong service chưa đủ để kết luận cấu hình đã hoàn chỉnh.
Biến môi trường
Biến môi trường là thông tin cấu hình truyền cho tiến trình, thường có dạng TÊN=giá_trị. Ví dụ MODE=demo có thể được ứng dụng sử dụng để chọn cách hoạt động; ý nghĩa cụ thể phụ thuộc ứng dụng. Công cụ đưa dữ liệu từ -e hoặc --env vào environment. Đây không phải mã hóa hay cơ chế giữ bí mật: giá trị có thể đọc được trong YAML, vì vậy cần kiểm tra trước khi chia sẻ.
Đầu vào cần có và ý nghĩa của YAML đầu ra
Những thông tin được tiếp nhận
Đầu vào là một chuỗi bắt đầu bằng docker run và có thành phần được bộ phân tích nhận làm image. Kiểm tra tiền tố có phân biệt chữ hoa, chữ thường. Với tùy chọn có giá trị, nên viết thành hai phần riêng, chẳng hạn --name demo_web hoặc -p 8080:80, thay vì gắn giá trị trực tiếp vào cờ.
Bảng dưới đây chỉ mô tả các tùy chọn được mã nhận diện, không phải danh sách đầy đủ của Docker:
| Tùy chọn đầu vào | Trường kết quả | Cách tiếp nhận |
|---|---|---|
-p, --publish |
ports |
Thêm giá trị vào danh sách cổng |
-v, --volume |
volumes |
Thêm giá trị vào danh sách lưu trữ |
-e, --env |
environment |
Thêm giá trị vào danh sách môi trường |
--name |
container_name |
Nhận tên container |
--restart |
restart |
Nhận chính sách khởi động lại |
--network, --net |
networks |
Thêm tên mạng vào danh sách |
--privileged |
privileged |
Ghi giá trị true |
Các giá trị cổng, volume và môi trường được tiếp nhận như chuỗi. Công cụ không kiểm tra đầy đủ miền giá trị, đường dẫn hay tính hợp lệ theo Docker.
Cách đọc các trường kết quả
YAML bắt đầu bằng version: '3.8', tiếp theo là services và một service có trường image. Những trường tùy chọn chỉ xuất hiện khi có dữ liệu tương ứng. Nếu có các thành phần được thu thập sau image, chúng được ghép thành command.
Đầu vào rỗng làm kết quả trống và chuyển trạng thái sang chờ nhập liệu. Sai tiền tố hoặc không tìm thấy image sẽ làm kết quả bị xóa và hiển thị lỗi. Ngược lại, thông báo thành công chỉ xác nhận đã sinh được văn bản. Kết quả có chức năng sao chép, nhưng mã không cho thấy chức năng tải xuống, triển khai hay kiểm thử cấu hình bằng Docker Compose.
Công cụ chuyển lệnh thành cấu hình theo quy tắc nào?
Từ chuỗi lệnh đến các trường cấu hình
Trước hết, công cụ xử lý cách nối dòng bằng dấu gạch chéo ngược trước xuống dòng, gộp các chuỗi khoảng trắng thành một dấu cách rồi bỏ khoảng trắng ở hai đầu. Sau kiểm tra tiền tố, lệnh được tách thành các phần để đọc lần lượt. Cách tách có nhận diện đoạn bọc bằng nháy đơn hoặc nháy kép, nhưng không phải bộ phân tích đầy đủ của shell.
Khi gặp cờ được hỗ trợ, công cụ lấy phần kế tiếp làm giá trị tương ứng. Cổng, volume, môi trường và mạng có thể được thêm nhiều lần. Với tên container và chính sách khởi động lại, giá trị gán sau cùng được giữ. Thành phần đầu tiên không bị nhận là cờ hoặc giá trị của cờ đã biết trở thành image.
Các thành phần thông thường sau image được thu thập rồi nối bằng dấu cách để tạo command. Tuy nhiên, bộ phân tích vẫn nhận diện cờ ở vị trí này. Vì thế, đối số bắt đầu bằng dấu gạch ngang của chương trình trong container có thể bị bỏ qua hoặc hiểu nhầm thành tùy chọn Docker.
Các cờ -d, --detach, -it, -i, -t và --rm bị bỏ qua. Với --user, --workdir, --hostname, các dạng ngắn tương ứng, --mac-address và --ip, công cụ bỏ cả cờ lẫn phần kế tiếp. Việc bỏ qua không đồng nghĩa hành vi ban đầu được bảo toàn.
Đặt tên service và khai báo mạng
Nếu có tên container, công cụ dùng tên đó làm cơ sở đặt tên service, chỉ giữ chữ cái Latin, chữ số, gạch dưới và gạch ngang. Nếu không có tên container, service được gọi là app. Trường container_name vẫn giữ giá trị đầu vào, nên tên service và tên container có thể khác nhau.
Tên mạng được đưa vào danh sách networks của service. Với tên khác host và bridge, công cụ bổ sung khai báo mạng ở cấp cao nhất với external: true. Đây chỉ là cách sinh cấu hình, không phải thao tác tạo hay kiểm tra mạng.
Cuối cùng, YAML được ghép trực tiếp từ các giá trị đã nhận. Cổng được bọc nháy kép; nhiều giá trị khác được ghi nguyên dạng. Không có bước xác thực YAML hoặc kiểm tra bằng Docker, nên ký tự đặc biệt và nội dung không hợp lệ vẫn cần được rà soát.
Ví dụ chuyển đổi và cách đánh giá kết quả
Các ví dụ sau dùng dữ liệu giả định, không chứa thông tin nhạy cảm. Kết quả được suy luận từ mã được cung cấp, không phải kết quả đã chạy thử.
Service web với cổng và chính sách khởi động lại
Giả sử đầu vào là:
docker run -d -p 8080:80 --name demo_web --restart always nginx:alpine
Công cụ bỏ qua -d, nhận image, tên container, chính sách khởi động lại và một ánh xạ cổng. Kết quả suy ra là:
version: '3.8'
services:
demo_web:
image: nginx:alpine
container_name: demo_web
restart: always
ports:
- "8080:80"
Trong ánh xạ này, 8080 là số cổng phía máy chủ, còn 80 là số cổng phía container, không phải số lượng hay tốc độ. Tên service giữ nguyên vì không có ký tự cần loại bỏ. Trường restart thể hiện chính sách được chép từ lệnh; kết quả không chứng minh website đã hoạt động hoặc cổng 8080 đang khả dụng.
Tác vụ có biến môi trường và mạng ngoài
Giả sử đầu vào dùng biến môi trường không nhạy cảm và một tên mạng minh họa:
docker run --name job.demo -e MODE=demo --network demo_net alpine:3.20 echo hello
Service được đặt tên jobdemo do dấu chấm bị loại bỏ, trong khi tên container vẫn là job.demo. Hai từ cuối được ghép thành lệnh thực thi:
version: '3.8'
services:
jobdemo:
image: alpine:3.20
container_name: job.demo
environment:
- MODE=demo
networks:
- demo_net
command: echo hello
networks:
demo_net:
external: true
Khai báo external: true cho thấy cấu hình coi demo_net là mạng ngoài. Nó không chứng minh mạng tồn tại. Tương tự, việc có command: echo hello chỉ cho biết văn bản đã được tạo, không phải tác vụ đã thực thi.
Hai trường hợp cần kiểm tra kỹ trước khi dùng
Với lệnh docker run -v demo_data:/data alpine:3.20, công cụ tạo service app, dùng image đã nêu và thêm demo_data:/data vào danh sách volume. Giả định demo_data là named volume, kết quả vẫn thiếu khai báo volume đó ở cấp cao nhất. Phần ánh xạ đã được chép, nhưng cấu hình lưu trữ cần được hoàn thiện và kiểm tra.
Với lệnh docker run --label demo=1 nginx:alpine, cờ nhãn không được hỗ trợ. Công cụ bỏ cờ nhưng không bỏ giá trị đi kèm, nên có thể nhận demo=1 làm image và nginx:alpine làm command. Trạng thái vẫn có thể là thành công. Đây là ví dụ rõ ràng cho việc phải kiểm tra nội dung kết quả thay vì chỉ nhìn thông báo.
Khi nào nên dùng bản chuyển đổi làm cấu hình khởi đầu?
Bản chuyển đổi phù hợp khi bạn có một lệnh đơn lẻ với các tùy chọn phổ biến và muốn ghi lại nó dưới dạng dễ đọc hơn. Một lệnh chứa nhiều ánh xạ cổng, biến môi trường hoặc vị trí lưu trữ có thể khó rà soát khi nằm trên một dòng. Việc tách thành các trường giúp bạn đối chiếu từng nhóm thông tin và trao đổi cấu hình rõ ràng hơn.
Công cụ cũng hữu ích khi học quan hệ giữa tùy chọn dòng lệnh và cấu trúc Compose. Bạn có thể quan sát -p tương ứng với ports, -e với environment và --restart với restart. Tuy nhiên, chỉ nên rút ra quan hệ từ những tùy chọn thực sự được hỗ trợ; trường vắng mặt không chứng minh Compose không có khả năng mô tả tính năng đó.
Hãy coi kết quả là bản nháp cho một service. Nó không tự suy ra kiến trúc nhiều dịch vụ, quan hệ phụ thuộc hoặc nhu cầu triển khai. Nếu lệnh có tùy chọn ngoài phạm vi hỗ trợ, bạn cần đối chiếu thêm để tránh mất thiết lập quan trọng.
Trước khi đưa bản nháp vào môi trường làm việc, cần rà soát image, cổng, lưu trữ, mạng và lệnh thực thi theo điều kiện thực tế. Nếu chia sẻ cấu hình cho người khác, hãy loại bỏ thông tin nhạy cảm trong môi trường hoặc đối số. Giá trị của công cụ nằm ở việc hỗ trợ tổ chức thông tin, không phải thay thế trách nhiệm xác minh cấu hình.
Docker run và Docker Compose khác nhau ở cách quản lý cấu hình nào?
Về kiến thức nền, docker run biểu diễn yêu cầu tạo và chạy container thông qua các tùy chọn dòng lệnh. Docker Compose sử dụng cấu hình khai báo, trong đó service cùng những thiết lập liên quan được mô tả bằng YAML. Khác biệt chính ở đây là cách tổ chức và sử dụng thông tin cấu hình, không chỉ là thay đổi cách xuống dòng.
Dòng lệnh thường phù hợp để mô tả một thao tác trực tiếp. Cấu hình Compose giúp tập hợp các thiết lập thành từng trường để đọc và duy trì. Compose có thể mô tả nhiều service trong cùng một ứng dụng, nhưng đó là khả năng chung của Compose; công cụ này chỉ sinh một service mỗi lần và không tự kết nối nhiều lệnh thành một hệ thống.
Chuyển văn bản không tự bảo toàn mọi quyết định về vòng đời container, tương tác terminal hoặc mạng. Chẳng hạn, các cờ tương tác và tự xóa container bị bỏ qua trong mã hiện có. Vì vậy, bản chuyển đổi cần được xem xét theo mục đích sử dụng, thay vì mặc định là bản tương đương hoàn toàn của lệnh ban đầu.
So với bản nháp do công cụ sinh, cấu hình được rà soát thủ công có thể bổ sung những phần thiếu hoặc điều chỉnh cách biểu diễn cho phù hợp môi trường đích. Ngoài ra, việc công cụ luôn ghi version: '3.8' chỉ phản ánh quy tắc sinh văn bản của nó. Không đủ thông tin để khẳng định trường này cần thiết hoặc được xử lý giống nhau trong mọi phiên bản Compose.
Giới hạn và những điểm phải rà soát trước khi sử dụng
Các trường hợp dễ chuyển sai hoặc thiếu
Cờ chưa hỗ trợ có tham số có thể làm lệch vị trí image. Dạng dấu bằng như --name=demo_web và dạng viết gộp như -p8080:80 không được nhận diện đáng tin cậy. Sau image, đối số bắt đầu bằng dấu gạch ngang vẫn có thể bị xử lý như cờ Docker, làm thay đổi command mong muốn.
Khoảng trắng được chuẩn hóa trước khi tách lệnh, kể cả trong phần có dấu nháy. Khi command được nối lại, ranh giới đối số ban đầu không được bảo toàn đầy đủ. Bộ sinh YAML ghép chuỗi trực tiếp, nên ký tự đặc biệt có thể gây lỗi cú pháp hoặc thay đổi cách hiểu giá trị.
Named volume không được thêm khai báo cấp cao nhất. Mạng ngoài cần được kiểm tra sự tồn tại; riêng host và bridge vẫn nằm trong danh sách mạng của service, không được chuyển thành network_mode. Tên service cũng có thể rỗng nếu tên container chỉ chứa ký tự bị lọc.
Kiểm tra cấu hình và thông tin nhạy cảm
Trước khi sử dụng, hãy đối chiếu những điểm sau với lệnh gốc và môi trường đích:
- Image có đúng tên và phiên bản mong muốn không?
- Cổng máy chủ và cổng container có đúng thứ tự không?
- Mount, named volume và mạng có đủ khai báo không?
- Biến môi trường và command có giữ đúng ý nghĩa không?
- Có tùy chọn quan trọng nào bị mất hoặc nhận nhầm không?
Thông báo thành công không thay thế việc xác thực bằng Docker Compose. Chức năng sao chép còn phụ thuộc trình duyệt và quyền clipboard. Ngoài ra, chưa đủ dữ liệu để cam kết toàn bộ trang không lưu hoặc không truyền nội dung nhập. Thông tin nhạy cảm có thể xuất hiện nguyên dạng trong đầu ra, nên không đưa bí mật thật vào chỉ để thử chuyển đổi.
Câu hỏi thường gặp
Vì sao báo chuyển đổi thành công nhưng Compose vẫn không chạy được?
Vì trạng thái thành công chỉ cho biết bộ phân tích và bộ sinh văn bản không phát sinh lỗi. Công cụ không xác thực YAML bằng một bộ phân tích độc lập, không kiểm tra image và không chạy Docker Compose. Một cờ chưa hỗ trợ có giá trị đi kèm còn có thể khiến giá trị đó bị nhận làm image. Bạn cần kiểm tra trường image trước, sau đó đối chiếu các khai báo cổng, lưu trữ, mạng và command với yêu cầu thực tế.
Tại sao tùy chọn –user, –workdir hoặc –rm không xuất hiện trong kết quả?
Vì mã chủ động bỏ qua những tùy chọn này. Với --user và --workdir, công cụ bỏ cả tùy chọn lẫn phần kế tiếp; với --rm, chỉ cờ bị bỏ qua. Không có trường tương ứng được tạo từ chúng. Điều đó không có nghĩa các lựa chọn ấy không quan trọng hoặc Compose tự giữ nguyên hành vi. Nếu ứng dụng phụ thuộc người dùng thực thi, thư mục làm việc hoặc cách quản lý vòng đời container, bản nháp cần được rà soát thêm.
Có nên nhập tùy chọn dạng –name=demo_web hoặc -p8080:80 không?
Không nên dùng các dạng đó nếu muốn bộ chuyển đổi hiện tại nhận diện đúng. Mã so khớp với cờ riêng như --name hoặc -p rồi lấy phần kế tiếp làm giá trị. Cách phù hợp là --name demo_web và -p 8080:80. Đây là giới hạn của công cụ, không phải kết luận về mọi cách viết mà Docker chấp nhận. Sau khi đổi cách viết, bạn vẫn cần kiểm tra kết quả vì công cụ chưa xác thực tính hợp lệ của giá trị.
Lệnh nhiều dòng và giá trị trong dấu nháy có được giữ nguyên hoàn toàn không?
Không. Công cụ có xử lý kiểu nối dòng bằng dấu gạch chéo ngược và nhận diện một số đoạn bọc dấu nháy, nhưng không mô phỏng đầy đủ shell. Việc gộp khoảng trắng diễn ra trước khi tách thành các phần, nên nhiều dấu cách bên trong giá trị có thể bị thu gọn. Dấu nháy bọc ngoài được loại bỏ, còn các phần command được nối lại bằng dấu cách. Vì vậy, cần đặc biệt kiểm tra giá trị có khoảng trắng, ký tự escape hoặc cấu trúc dấu nháy phức tạp.
Vì sao cấu hình dùng mạng host, bridge hoặc mạng external cần kiểm tra lại?
Vì công cụ áp dụng quy tắc sinh mạng đơn giản thay vì chuyển đổi đầy đủ ý nghĩa từng chế độ mạng. host và bridge được đưa vào danh sách mạng của service nhưng không có khai báo tương ứng ở cấp cao nhất, cũng không được biểu diễn bằng network_mode. Tên mạng khác được đánh dấu external: true. Bạn cần xác minh cách biểu diễn phù hợp với môi trường Compose và kiểm tra mạng ngoài đã tồn tại; công cụ không thực hiện hai việc đó.
Tại sao named volume trong lệnh chưa được khai báo đầy đủ trong YAML?
Vì bộ sinh kết quả chỉ đưa giá trị từ -v hoặc --volume vào danh sách lưu trữ bên trong service. Mã không bổ sung phần khai báo named volume ở cấp cao nhất và không phân biệt nó với bind mount. Do đó, việc tên volume xuất hiện trong ánh xạ không chứng minh mọi khai báo cần thiết đã có. Hãy xác định loại lưu trữ mà lệnh gốc muốn dùng, rồi rà soát cấu hình tương ứng trước khi sử dụng dữ liệu thực tế.
Công cụ có chạy Docker hoặc bảo đảm không lưu dữ liệu tôi nhập không?
Không có chức năng chạy Docker được xác nhận trong mã đã cung cấp. Công cụ tạo văn bản YAML và hỗ trợ sao chép bằng clipboard của trình duyệt. Đồng thời, chưa đủ thông tin về toàn bộ trang và các thành phần triển khai để bảo đảm nội dung nhập không được lưu hoặc truyền đi. Không nên suy ra cam kết riêng tư chỉ từ đoạn mã chuyển đổi. Nếu lệnh chứa thông tin nhạy cảm, hãy dùng dữ liệu thay thế khi tham khảo và kiểm tra cả đầu vào lẫn đầu ra trước khi chia sẻ.


19 bình luận
Trần Thị Hà
Tool khá ổn áp, cứu mình khỏi màn ngồi dò từng option :)) Có thêm tô màu cú pháp nữa là đẹp.
Hoàng Văn Phúc
IPv6 ULA generator nằm trong mục công cụ tương tự làm mình tò mò. Có bài giải thích lúc nào cần dùng địa chỉ ULA không?
Dương Quốc Bình
Góp ý thêm nút sao chép kết quả kèm thông báo đã copy nhé, dùng trên điện thoại sẽ tiện hơn nhiều.
Trần Hữu Thành
ad oi lenh co volume duong dan tren Windows thi nhap kieu nao cho dung vay?
Nguyễn Quốc Nam
Mình mong phần kết quả ghi chú những tham số không chuyển đổi được, để tránh tưởng cấu hình Compose đã giữ đủ mọi tùy chọn.
Đặng Đức Hùng
Cho mình hỏi cổng -p 8080:80 khi chuyển sang Compose thì thứ tự vẫn là cổng máy chủ trước, cổng container sau nhỉ?
Ngô Hoài Tài
Nếu lệnh docker run có mật khẩu trong biến môi trường thì dữ liệu được xử lý ngay trên trình duyệt hay gửi lên máy chủ vậy?
Đỗ Thị Hà
Xịn nha, trước toàn tự chuyển rồi sai thụt dòng YAML 😂
Đặng Xuân Hà
Chuyển lệnh chạy Redis sang Compose nhanh gọn ghê. Vẫn nên xem lại cấu hình trước khi chạy, nhưng tiết kiệm kha khá thời gian.
Đặng Thị Quang
Có hỗ trợ lệnh xuống dòng bằng dấu \ không? Mình thường copy lệnh nhiều dòng từ tài liệu, muốn dán nguyên khối cho tiện.
Ngô Xuân Quang
Có hỗ trợ chuyển lệnh dùng –network host không ad? Mình đang chạy một dịch vụ cần cấu hình này.
Dương Hoài Tài
Mình thích phần thuật ngữ Docker Compose bên dưới. Mấy mục ports, volumes với restart giải thích vừa đủ, không bị quá hàn lâm.
Lê Xuân Dũng
Mong có thông báo rõ khi nhập sai cú pháp. Mình dán thiếu tên image mà chưa biết cần sửa ở đâu.
Trần Quốc Yến
tool ngon nhung neu co vi du docker run mau de bam thu thi se de lam quen hon
Lý Tuấn Tú
Ước gì có thêm nút tải xuống file compose.yaml, mình khỏi phải copy rồi tạo file thủ công.
Đỗ Hoài Dương
Phần giải thích Docker run và Docker Compose khác nhau thế nào khá dễ hiểu, đọc xong mới biết khi nào nên dùng cái nào.
Nguyễn Thanh Vinh
giao dien de dung, dan lenh vao la co cau hinh compose. rat hop voi nguoi moi nhu minh
Trần Đức Phúc
Cho mình hỏi lệnh có nhiều tham số -e thì công cụ sẽ đưa hết vào phần environment đúng không?
Phan Xuân Khánh
Mình vừa chuyển lệnh docker run khá dài sang Compose, đỡ phải ngồi viết YAML từng dòng. Công cụ tiện thật!