Làm cách nào để sao chép toàn bộ thư mục tệp vào thư mục hiện có bằng Python?


209

Chạy đoạn mã sau từ thư mục chứa thư mục có tên bar(chứa một hoặc nhiều tệp) và thư mục có tên baz(cũng chứa một hoặc nhiều tệp). Hãy chắc chắn rằng không có một thư mục có tên foo.

import shutil
shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo')

Nó sẽ thất bại với:

$ python copytree_test.py 
Traceback (most recent call last):
  File "copytree_test.py", line 5, in <module>
    shutil.copytree('baz', 'foo')
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/shutil.py", line 110, in copytree
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/os.py", line 172, in makedirs
OSError: [Errno 17] File exists: 'foo'

Tôi muốn nó hoạt động theo cách tương tự như tôi đã gõ:

$ mkdir foo
$ cp bar/* foo/
$ cp baz/* foo/

Tôi có cần sử dụng shutil.copy()để sao chép từng tập tin bazvào fookhông? (Sau khi tôi đã sao chép nội dung của 'thanh' vào 'foo' với shutil.copytree()?) Hoặc có cách nào dễ dàng / tốt hơn không?


1
FYI: đây là chức năng copytree ban đầu, chỉ cần sao chép và vá nó :)
schlamar

3
Có một vấn đề Python về việc thay đổi shutil.copytree()hành vi của việc cho phép ghi vào thư mục hiện có, nhưng có một số chi tiết hành vi cần phải được thống nhất.
Nick Chammas

2
Chỉ cần lưu ý rằng yêu cầu tăng cường nói trên đã được thực hiện cho Python 3.8: docs.python.org/3.8/whatsnew/3.8.html#shutil
ncoghlan

Câu trả lời:


174

Hạn chế này của tiêu chuẩn shutil.copytreecó vẻ tùy tiện và gây phiền nhiễu. Cách giải quyết:

import os, shutil
def copytree(src, dst, symlinks=False, ignore=None):
    for item in os.listdir(src):
        s = os.path.join(src, item)
        d = os.path.join(dst, item)
        if os.path.isdir(s):
            shutil.copytree(s, d, symlinks, ignore)
        else:
            shutil.copy2(s, d)

Lưu ý rằng nó không hoàn toàn phù hợp với tiêu chuẩn copytree:

  • nó không tôn vinh symlinksignoretham số cho thư mục gốc của srccây;
  • nó không gây ra shutil.Errorlỗi ở cấp độ gốc src;
  • trong trường hợp có lỗi trong quá trình sao chép một cây con, nó sẽ tăng shutil.Errorcho cây con đó thay vì cố gắng sao chép các cây con khác và tăng đơn lẻ kết hợp shutil.Error.

50
Cảm ơn! Đồng ý rằng điều này có vẻ hoàn toàn độc đoán! shutil.copytreelàm một os.makedirs(dst)lúc bắt đầu. Không có phần nào của mã thực sự có vấn đề với một thư mục tồn tại từ trước. Điều này cần phải được thay đổi. Ít nhất cung cấp một exist_ok=Falsetham số cho cuộc gọi
cfi

6
Đây là một câu trả lời hay - tuy nhiên câu trả lời của Mital Vora dưới đây cũng đáng để xem xét. Họ đã gọi copytree một cách đệ quy thay vì gọi shutil.copytree () vì cùng một vấn đề sẽ phát sinh. Có thể xem xét hợp nhất câu trả lời hoặc cập nhật lên Mital Vora.
PJeffes

4
Điều này không thành công nếu được cung cấp một đường dẫn bao gồm một thư mục không trống ở đích. Có lẽ ai đó có thể giải quyết điều này bằng đệ quy đuôi nhưng đây là một sửa đổi cho mã của bạn hoạt độngdef copyTree( src, dst, symlinks=False, ignore=None): for item in os.listdir(src): s = os.path.join(src, item) d = os.path.join(dst, item) if os.path.isdir(s): if os.path.isdir(d): self.recursiveCopyTree(s, d, symlinks, ignore) else: shutil.copytree(s, d, symlinks, ignore) else: shutil.copy2(s, d)
Sojurn

8
Meh, siêu khó chịu. Đã 4 năm sau, và shutil.copytree vẫn còn hạn chế ngớ ngẩn này. :-(
antred

5
@antred ... nhưng distutils.dir_util.copy_tree(), cũng nằm trong stdlib, không có hạn chế nào như vậy và thực sự hành xử như mong đợi. Do đó, không có lý do thuyết phục nào để cố gắng hủy đăng ký triển khai ( ... thường bị hỏng ) của bạn. Câu trả lời của Brendan Abel hoàn toàn nên là giải pháp được chấp nhận ngay bây giờ.
Cecil Curry

256

Đây là một giải pháp là một phần của thư viện chuẩn:

from distutils.dir_util import copy_tree
copy_tree("/a/b/c", "/x/y/z")

Xem câu hỏi tương tự này.

Sao chép nội dung thư mục vào một thư mục với python


5
Đây là một cái tốt vì nó sử dụng thư viện tiêu chuẩn. Symlinks, chế độ và thời gian cũng có thể được bảo tồn.
itafire

1
Nhận thấy một bất lợi nhỏ. distutils.errors.DistutilsInternalError: mkpath: 'name' must be a string, tức là nó không chấp nhận PosixPath. Cần phải str(PosixPath). Danh sách mong muốn để cải thiện. Khác với vấn đề này, tôi thích câu trả lời này.
Gấu chó

@SunBear, Vâng, tôi nghĩ đó sẽ là trường hợp với hầu hết các thư viện khác lấy đường dẫn làm chuỗi. Một phần của nhược điểm của việc chọn không làm cho Pathđối tượng được kế thừa từ strtôi cho rằng, giống như hầu hết các triển khai trước đó của các đối tượng đường dẫn hướng đối tượng ..
Brendan Abel

Btw, tôi đã gặp một sự thiếu hụt tài liệu của chức năng này. Nó được ghi lại ở đây . Người dùng của chức năng này là lời khuyên để nhận thức được nó.
Gấu chó

1
Mặc dù "công khai về mặt kỹ thuật", xin lưu ý rằng các nhà phát triển của distutils đã nói rõ (cùng liên kết với @ SunBear's, thx!) distutils.dir_util.copy_tree()Được coi là chi tiết triển khai của distutils và không được khuyến nghị sử dụng cho công chúng. Giải pháp thực sự nên được shutil.copytree()cải thiện / mở rộng để hành xử giống hơn distutils.dir_util.copy_tree(), nhưng không có thiếu sót. Trong thời gian này, tôi sẽ tiếp tục sử dụng các chức năng của trình trợ giúp tùy chỉnh tương tự như một số chức năng được cung cấp trong các câu trả lời khác.
Boris Dalstein

61

Để cải thiện một chút về câu trả lời của atzz cho chức năng trong đó chức năng trên luôn cố gắng sao chép các tệp từ nguồn sang đích.

def copytree(src, dst, symlinks=False, ignore=None):
    if not os.path.exists(dst):
        os.makedirs(dst)
    for item in os.listdir(src):
        s = os.path.join(src, item)
        d = os.path.join(dst, item)
        if os.path.isdir(s):
            copytree(s, d, symlinks, ignore)
        else:
            if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
                shutil.copy2(s, d)

Trong triển khai trên của tôi

  • Tạo thư mục đầu ra nếu chưa tồn tại
  • Làm thư mục sao chép bằng cách gọi đệ quy phương thức của riêng tôi.
  • Khi chúng ta thực sự sao chép tập tin, tôi kiểm tra xem tập tin có bị sửa đổi không thì chúng ta chỉ nên sao chép.

Tôi đang sử dụng chức năng trên cùng với xây dựng scons. Nó giúp tôi rất nhiều vì mỗi lần tôi biên dịch, tôi có thể không cần sao chép toàn bộ tập tin .. mà chỉ cần sửa đổi các tập tin.


4
Đẹp, ngoại trừ việc bạn có các liên kết tượng trưng và bỏ qua làm đối số, nhưng chúng bị bỏ qua.
Matthew Alpert

Điều đáng chú ý là st_mtime granularity có thể là thô như 2 giây trên hệ thống tập tin FAT docs.python.org/2/library/os.html . Sử dụng mã này trong bối cảnh các cập nhật xảy ra liên tiếp nhanh chóng, bạn có thể thấy các phần ghi đè không diễn ra.
dGH

Có một lỗi trong dòng thứ hai đến cuối cùng, nên là: if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
mpderbec

34

Một sự hợp nhất lấy cảm hứng từ atzz và Mital Vora:

#!/usr/bin/python
import os
import shutil
import stat
def copytree(src, dst, symlinks = False, ignore = None):
  if not os.path.exists(dst):
    os.makedirs(dst)
    shutil.copystat(src, dst)
  lst = os.listdir(src)
  if ignore:
    excl = ignore(src, lst)
    lst = [x for x in lst if x not in excl]
  for item in lst:
    s = os.path.join(src, item)
    d = os.path.join(dst, item)
    if symlinks and os.path.islink(s):
      if os.path.lexists(d):
        os.remove(d)
      os.symlink(os.readlink(s), d)
      try:
        st = os.lstat(s)
        mode = stat.S_IMODE(st.st_mode)
        os.lchmod(d, mode)
      except:
        pass # lchmod not available
    elif os.path.isdir(s):
      copytree(s, d, symlinks, ignore)
    else:
      shutil.copy2(s, d)
  • Hành vi tương tự như shutil.copytree , với các liên kết tượng trưngbỏ qua các tham số
  • Tạo cấu trúc đích thư mục nếu không tồn tại
  • Sẽ không thất bại nếu dst đã tồn tại

Điều này nhanh hơn nhiều so với giải pháp ban đầu khi thư mục lồng nhau sâu. Cảm ơn
Kashif

Bạn đã xác định một hàm cũng có tên 'bỏ qua' trong mã ở nơi khác chưa?
KenV99

Bạn có thể xác định bất kỳ chức năng nào với bất kỳ tên nào bạn thích trước khi gọi chức năng copytree. Hàm này (cũng có thể là biểu thức lambda) có hai đối số: tên thư mục và các tệp trong đó, nó sẽ trả về một lần lặp các tệp bỏ qua.
Cyrille Pontvieux

[x for x in lst if x not in excl]điều này không làm giống như copytree, sử dụng khớp mẫu toàn cục. vi.wikipedia.org/wiki/Glob_(programming)
Konstantin Schubert

2
Điều đó thật tuyệt. Bỏ qua đã không được sử dụng chính xác trong câu trả lời ở trên.
Keith Holliday

21

Python 3.8 đã giới thiệu dirs_exist_okđối số cho shutil.copytree:

Đệ quy sao chép toàn bộ cây thư mục bắt nguồn từ src vào thư mục có tên dst và trả về thư mục đích. dirs_exist_ok ra lệnh xem có nên đưa ra một ngoại lệ trong trường hợp dst hoặc bất kỳ thư mục cha bị thiếu nào đã tồn tại.

Do đó, với Python 3.8+, điều này sẽ hoạt động:

import shutil

shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo', dirs_exist_ok=True)

dirs_exist_ok=Falsetheo mặc định trong copytree, lần thử đầu tiên sẽ thất bại?
Jay

1
@Jay, chỉ khi thư mục đã tồn tại. Tôi đã bỏ dirs_exist_okqua cuộc gọi đầu tiên để minh họa sự khác biệt (và vì thư mục chưa tồn tại trong ví dụ của OP), nhưng tất nhiên bạn có thể sử dụng nó nếu bạn muốn.
Chris

Cảm ơn, nếu bạn thêm một bình luận gần bản sao đầu tiên, tôi nghĩ nó sẽ làm cho nó rõ ràng hơn :)
Jay

7

tài liệu tuyên bố rõ ràng rằng thư mục đích không nên tồn tại :

Thư mục đích, được đặt tên theo dst, không được tồn tại; nó sẽ được tạo ra cũng như thiếu các thư mục cha.

Tôi nghĩ rằng đặt cược tốt nhất của bạn là os.walkthứ hai và tất cả các thư mục, copy2thư mục và tập tin hệ quả và làm thêm copystatcho các thư mục. Sau tất cả, đó chính xác là những gì được copytreegiải thích trong các tài liệu. Hoặc bạn có thể copycopystattừng thư mục / tập tin và os.listdirthay vì os.walk.


1

Điều này được lấy cảm hứng từ câu trả lời tốt nhất ban đầu được cung cấp bởi atzz, tôi chỉ cần thêm logic tệp / thư mục thay thế. Vì vậy, nó không thực sự hợp nhất, nhưng xóa tệp / thư mục hiện có và sao chép tệp mới:

import shutil
import os
def copytree(src, dst, symlinks=False, ignore=None):
    for item in os.listdir(src):
        s = os.path.join(src, item)
        d = os.path.join(dst, item)
        if os.path.exists(d):
            try:
                shutil.rmtree(d)
            except Exception as e:
                print e
                os.unlink(d)
        if os.path.isdir(s):
            shutil.copytree(s, d, symlinks, ignore)
        else:
            shutil.copy2(s, d)
    #shutil.rmtree(src)

Uncomment rmtree để làm cho nó một chức năng di chuyển.


0

Đây là phiên bản của tôi của cùng một nhiệm vụ ::

import os, glob, shutil

def make_dir(path):
    if not os.path.isdir(path):
        os.mkdir(path)


def copy_dir(source_item, destination_item):
    if os.path.isdir(source_item):
        make_dir(destination_item)
        sub_items = glob.glob(source_item + '/*')
        for sub_item in sub_items:
            copy_dir(sub_item, destination_item + '/' + sub_item.split('/')[-1])
    else:
        shutil.copy(source_item, destination_item)

0

Đây là một phiên bản lấy cảm hứng từ chủ đề này bắt chước chặt chẽ hơn distutils.file_util.copy_file.

updateonlylà một bool nếu True, sẽ chỉ sao chép các tệp có ngày sửa đổi mới hơn các tệp hiện có dsttrừ khi được liệt kê trong forceupdateđó sẽ sao chép bất kể.

ignoreforceupdatemong đợi danh sách tên tệp hoặc thư mục / tên tệp liên quan đến src và chấp nhận ký tự đại diện kiểu Unix tương tự globhoặc fnmatch.

Hàm trả về danh sách các tệp được sao chép (hoặc sẽ được sao chép dryrunnếu đúng).

import os
import shutil
import fnmatch
import stat
import itertools

def copyToDir(src, dst, updateonly=True, symlinks=True, ignore=None, forceupdate=None, dryrun=False):

    def copySymLink(srclink, destlink):
        if os.path.lexists(destlink):
            os.remove(destlink)
        os.symlink(os.readlink(srclink), destlink)
        try:
            st = os.lstat(srclink)
            mode = stat.S_IMODE(st.st_mode)
            os.lchmod(destlink, mode)
        except OSError:
            pass  # lchmod not available
    fc = []
    if not os.path.exists(dst) and not dryrun:
        os.makedirs(dst)
        shutil.copystat(src, dst)
    if ignore is not None:
        ignorepatterns = [os.path.join(src, *x.split('/')) for x in ignore]
    else:
        ignorepatterns = []
    if forceupdate is not None:
        forceupdatepatterns = [os.path.join(src, *x.split('/')) for x in forceupdate]
    else:
        forceupdatepatterns = []
    srclen = len(src)
    for root, dirs, files in os.walk(src):
        fullsrcfiles = [os.path.join(root, x) for x in files]
        t = root[srclen+1:]
        dstroot = os.path.join(dst, t)
        fulldstfiles = [os.path.join(dstroot, x) for x in files]
        excludefiles = list(itertools.chain.from_iterable([fnmatch.filter(fullsrcfiles, pattern) for pattern in ignorepatterns]))
        forceupdatefiles = list(itertools.chain.from_iterable([fnmatch.filter(fullsrcfiles, pattern) for pattern in forceupdatepatterns]))
        for directory in dirs:
            fullsrcdir = os.path.join(src, directory)
            fulldstdir = os.path.join(dstroot, directory)
            if os.path.islink(fullsrcdir):
                if symlinks and dryrun is False:
                    copySymLink(fullsrcdir, fulldstdir)
            else:
                if not os.path.exists(directory) and dryrun is False:
                    os.makedirs(os.path.join(dst, dir))
                    shutil.copystat(src, dst)
        for s,d in zip(fullsrcfiles, fulldstfiles):
            if s not in excludefiles:
                if updateonly:
                    go = False
                    if os.path.isfile(d):
                        srcdate = os.stat(s).st_mtime
                        dstdate = os.stat(d).st_mtime
                        if srcdate > dstdate:
                            go = True
                    else:
                        go = True
                    if s in forceupdatefiles:
                        go = True
                    if go is True:
                        fc.append(d)
                        if not dryrun:
                            if os.path.islink(s) and symlinks is True:
                                copySymLink(s, d)
                            else:
                                shutil.copy2(s, d)
                else:
                    fc.append(d)
                    if not dryrun:
                        if os.path.islink(s) and symlinks is True:
                            copySymLink(s, d)
                        else:
                            shutil.copy2(s, d)
    return fc

0

Giải pháp trước đây có một số vấn đề srccó thể ghi đè dstmà không có bất kỳ thông báo hoặc ngoại lệ nào.

Tôi thêm một predict_errorphương pháp để dự đoán lỗi trước khi sao chép. copytreechủ yếu dựa trên phiên bản của Cyrille Pontvieux.

Sử dụng predict_errorđể dự đoán tất cả các lỗi lúc đầu là tốt nhất, trừ khi bạn muốn thấy ngoại lệ được đưa ra lần khác khi thực hiện copytreecho đến khi sửa tất cả lỗi.

def predict_error(src, dst):  
    if os.path.exists(dst):
        src_isdir = os.path.isdir(src)
        dst_isdir = os.path.isdir(dst)
        if src_isdir and dst_isdir:
            pass
        elif src_isdir and not dst_isdir:
            yield {dst:'src is dir but dst is file.'}
        elif not src_isdir and dst_isdir:
            yield {dst:'src is file but dst is dir.'}
        else:
            yield {dst:'already exists a file with same name in dst'}

    if os.path.isdir(src):
        for item in os.listdir(src):
            s = os.path.join(src, item)
            d = os.path.join(dst, item)
            for e in predict_error(s, d):
                yield e


def copytree(src, dst, symlinks=False, ignore=None, overwrite=False):
    '''
    would overwrite if src and dst are both file
    but would not use folder overwrite file, or viceverse
    '''
    if not overwrite:
        errors = list(predict_error(src, dst))
        if errors:
            raise Exception('copy would overwrite some file, error detail:%s' % errors)

    if not os.path.exists(dst):
        os.makedirs(dst)
        shutil.copystat(src, dst)
    lst = os.listdir(src)
    if ignore:
        excl = ignore(src, lst)
        lst = [x for x in lst if x not in excl]
    for item in lst:
        s = os.path.join(src, item)
        d = os.path.join(dst, item)
        if symlinks and os.path.islink(s):
            if os.path.lexists(d):
                os.remove(d)
            os.symlink(os.readlink(s), d)
            try:
                st = os.lstat(s)
                mode = stat.S_IMODE(st.st_mode)
                os.lchmod(d, mode)
            except:
                pass  # lchmod not available
        elif os.path.isdir(s):
            copytree(s, d, symlinks, ignore)
        else:
            if not overwrite:
                if os.path.exists(d):
                    continue
            shutil.copy2(s, d)

0

Đây là vượt qua của tôi tại vấn đề. Tôi đã sửa đổi mã nguồn cho copytree để giữ chức năng ban đầu, nhưng bây giờ không có lỗi xảy ra khi thư mục đã tồn tại. Tôi cũng đã thay đổi nó để nó không ghi đè lên các tệp hiện có mà chỉ giữ cả hai bản sao, một bản có tên đã được sửa đổi, vì điều này rất quan trọng đối với ứng dụng của tôi.

import shutil
import os


def _copytree(src, dst, symlinks=False, ignore=None):
    """
    This is an improved version of shutil.copytree which allows writing to
    existing folders and does not overwrite existing files but instead appends
    a ~1 to the file name and adds it to the destination path.
    """

    names = os.listdir(src)
    if ignore is not None:
        ignored_names = ignore(src, names)
    else:
        ignored_names = set()

    if not os.path.exists(dst):
        os.makedirs(dst)
        shutil.copystat(src, dst)
    errors = []
    for name in names:
        if name in ignored_names:
            continue
        srcname = os.path.join(src, name)
        dstname = os.path.join(dst, name)
        i = 1
        while os.path.exists(dstname) and not os.path.isdir(dstname):
            parts = name.split('.')
            file_name = ''
            file_extension = parts[-1]
            # make a new file name inserting ~1 between name and extension
            for j in range(len(parts)-1):
                file_name += parts[j]
                if j < len(parts)-2:
                    file_name += '.'
            suffix = file_name + '~' + str(i) + '.' + file_extension
            dstname = os.path.join(dst, suffix)
            i+=1
        try:
            if symlinks and os.path.islink(srcname):
                linkto = os.readlink(srcname)
                os.symlink(linkto, dstname)
            elif os.path.isdir(srcname):
                _copytree(srcname, dstname, symlinks, ignore)
            else:
                shutil.copy2(srcname, dstname)
        except (IOError, os.error) as why:
            errors.append((srcname, dstname, str(why)))
        # catch the Error from the recursive copytree so that we can
        # continue with other files
        except BaseException as err:
            errors.extend(err.args[0])
    try:
        shutil.copystat(src, dst)
    except WindowsError:
        # can't copy file access times on Windows
        pass
    except OSError as why:
        errors.extend((src, dst, str(why)))
    if errors:
        raise BaseException(errors)

0

Thử cái này:

import os,shutil

def copydir(src, dst):
  h = os.getcwd()
  src = r"{}".format(src)
  if not os.path.isdir(dst):
     print("\n[!] No Such directory: ["+dst+"] !!!")
     exit(1)

  if not os.path.isdir(src):
     print("\n[!] No Such directory: ["+src+"] !!!")
     exit(1)
  if "\\" in src:
     c = "\\"
     tsrc = src.split("\\")[-1:][0]
  else:
    c = "/"
    tsrc = src.split("/")[-1:][0]

  os.chdir(dst)
  if os.path.isdir(tsrc):
    print("\n[!] The Directory Is already exists !!!")
    exit(1)
  try:
    os.mkdir(tsrc)
  except WindowsError:
    print("\n[!] Error: In[ {} ]\nPlease Check Your Dirctory Path !!!".format(src))
    exit(1)
  os.chdir(h)
  files = []
  for i in os.listdir(src):
    files.append(src+c+i)
  if len(files) > 0:
    for i in files:
        if not os.path.isdir(i):
            shutil.copy2(i, dst+c+tsrc)

  print("\n[*] Done ! :)")

copydir("c:\folder1", "c:\folder2")

0

Đây là một phiên bản mong đợi pathlib.Pathnhư là một đầu vào.

# Recusively copies the content of the directory src to the directory dst.
# If dst doesn't exist, it is created, together with all missing parent directories.
# If a file from src already exists in dst, the file in dst is overwritten.
# Files already existing in dst which don't exist in src are preserved.
# Symlinks inside src are copied as symlinks, they are not resolved before copying.
#
def copy_dir(src, dst):
    dst.mkdir(parents=True, exist_ok=True)
    for item in os.listdir(src):
        s = src / item
        d = dst / item
        if s.is_dir():
            copy_dir(s, d)
        else:
            shutil.copy2(str(s), str(d))

Lưu ý rằng hàm này yêu cầu Python 3.6, đây là phiên bản đầu tiên của Python có os.listdir()hỗ trợ các đối tượng giống như đường dẫn làm đầu vào. Nếu bạn cần hỗ trợ các phiên bản trước của Python, bạn có thể thay thế listdir(src)bằng listdir(str(src)).


-2

Tôi nghĩ rằng cách nhanh nhất và đơn giản nhất sẽ là python gọi các lệnh hệ thống ...

thí dụ..

import os
cmd = '<command line call>'
os.system(cmd)

Tar và gzip lên thư mục .... giải nén và giải nén thư mục ở vị trí mong muốn.

vâng


nếu bạn đang chạy trong windows ... hãy tải xuống 7zip .. và sử dụng dòng lệnh cho điều đó. ... lại chỉ là gợi ý.
Kirby

31
Các lệnh hệ thống phải luôn là giải pháp cuối cùng. Luôn luôn tốt hơn để sử dụng thư viện tiêu chuẩn bất cứ khi nào có thể để mã của bạn có thể mang theo được.
jathanism
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.