Добавить строку в файл ruby
У меня есть файл конфигурации, в который я хочу добавить строку, которая выглядит, например, как это:
Новую строку следует не добавлять, а записывать где-нибудь в середине файла. Поэтому я ищу определенную позицию (или строку) в файле, и когда она была найдена, я вставляю свою новую строку:
Проблема в том, что Ruby перезаписывает строку в этой позиции новым текстом, поэтому результат будет следующим:
Я хочу "настоящую" вставку:
Как этого добиться?
Если это небольшой файл, я думаю, что проще всего:
- Прочтите полный файл.
- Вставьте строку.
- Напишите новый файл.
Или вы можете скопировать его построчно, а затем перезаписать оригинал с помощью mv следующим образом:
И если вы хотите добавить к существующему .
Еще пара вариантов:
Самый простой способ - прочитать весь файл в памяти, затем записать первую часть обратно в файл, записать вставленную строку в файл и записать оставшуюся часть обратно в файл. Это должно быть относительно просто сделать, когда вы читаете файл как массив строк, но вы можете столкнуться с проблемами, если ваш файл очень большой, поскольку при таком подходе вам нужно прочитать весь файл в память.
В качестве альтернативы вы можете найти место, в которое вы хотите вставить строку, прочитать строки после этой точки в памяти, вернуться к этой точке в файле, записать новую строку в файл и, наконец, записать оставшиеся строки в файл. Опять же, вы столкнетесь с проблемами, если оставшаяся часть файла будет очень большой, так как вам придется прочитать все это в памяти.
Третий подход - записать первую часть в новый файл, записать вставленную строку в новый файл, записать остаток исходного файла в новый файл и, наконец, заменить старый файл новым файлом в файловой системе. Этот подход позволяет вам работать с одной строкой за раз, поэтому вы можете обрабатывать файлы, которые не помещаются в памяти.
Причина, по которой запись файла работает так, заключается в том, что файл похож на массив байтов фиксированного размера: когда вы записываете байт в файл, вы перезаписываете существующий байт (я игнорирую случай, когда вы добавляете в файл здесь) . Таким образом, единственный способ вставить что-либо в файл - сначала переместить старый контент в новое место, прочитав его из старого места и записав в новое место. После этого вы можете «вставить» данные в освободившееся место.
I need to read the data out of database and then save it in a text file.
How can I do that in Ruby? Is there any file management system in Ruby?
7 Answers 7
Are you looking for the following?
@vish: I wouldn't recommend this solution as the file descriptor would be left open if f.write raises an Exception.
You can use the short version:
It returns the length written; see ::write for more details and options.
To append to the file, if it already exists, use:
FYI this shorthand method only works beginning with Ruby 1.9.3. There is, no such method in any earlier versions of 1.9 or 1.8. In that case you must use the longer block method posted by @mvndaai
This is preferred approach in most cases:
When a block is passed to File.open , the File object will be automatically closed when the block terminates.
If you don't pass a block to File.open , you have to make sure that file is correctly closed and the content was written to file.
You can find it in documentation:
Just something ruby way-ish: nil is an object, so to check if a file is null, you ask the object itself instead of comparing (file.nil? instead of file == nil)
finally an answer that shows that one should also check for file status and how to handle it, and not just one liner that just shows the open call.
Thanks @Geoff. It's good to see new users cleaning up outdated questions and answers. Makes for a better site overall.
I find this ironic. The answer is very well documented. but now a year later, this question is the first hit on Google. When the question was asked, it may have seemed that the OP was dedicating little effort but now as far as Google is concerned, this is the best source.
Probably because everything you really need to know is here. Mine is the "teach a man to fish" answer for those who want to read the finer details, and there are plenty of "give a man a fish" answers here as well for people who just want to cut-and-paste. It's not surprising that this combination ends up well ranked on Google.
So, in fairness, I work in a lot of different languages, which means I rarely get around to remembering the syntax for any specific one. I've googled this a few times, and I always scroll right past this answer, to the one below it, because when I google StackOverflow I'm usually just looking for a dang fish. :p Honestly tho it's good having both. Perhaps someday I will be doing enough Ruby all at once that I will care about the details.
This does not answer the question. It merely points to the two classes that contain most of the methods for doing I/O, and makes no mention of the connection with the database. That would be fine as a comment, but hardly qualifies as an answer. I realise that the OP and the many readers who upvoted this answer disagree, and I frankly don't understand what they were thinking.
Zambri's answer found here is the best.
r - Read only. The file must exist.
w - Create an empty file for writing.
a - Append to a file.The file is created if it does not exist.
r+ - Open a file for update both reading and writing. The file must exist.
w+ - Create an empty file for both reading and writing.
a+ - Open a file for reading and appending. The file is created if it does not exist.
In your case, w is preferable.
@CarySwoveland I actually agree with you. The real problem is that one of the two questions should have been marked as a duplicate a long time ago. I copied the answer because once I found the question zanbri had answered and the next few times when I needed the same info I came across this question first and had to figure out how to get to the other question. Eventually, I thought it would just be easier to have his answer here as well. I linked to his answer so hopefully, people would click over and give him an upvote as well.
I'm trying to create a new file and things don't seem to be working as I expect them too. Here's what I've tried:
According to everything I've read online all of those should work but every single one of them gives me this:
This happens from IRB as well as a Ruby script. What am I missing?
@muistooshort That's the only conclusion I can reach. A permissions error would have thrown Errno::EACCES , not ENOENT .
OK, now I feel stupid. The first two definitely do not work but the second two do. Not sure how I convinced my self that I had tried them. Sorry for wasting everyone's time.
9 Answers 9
- r - Read only. The file must exist.
- w - Create an empty file for writing.
- a - Append to a file.The file is created if it does not exist.
- r+ - Open a file for update both reading and writing. The file must exist.
- w+ - Create an empty file for both reading and writing.
- a+ - Open a file for reading and appending. The file is created if it does not exist.
In your case, 'w' is preferable.
OR you could have:
great answer. Ruby conevntion is snake case for var names. Just a heads up for newbies. outFile should look like out_file .
@jkdev, yes, it will be closed, but it's still considered code smell to rely on that, just as if the programmer never closed files and let the OS close the files when the interpreter terminates. And both can lead to a bad bug if multiple files are opened and the code never closes them leading to an out-of-handles condition and error. It's just a better, safer, practice to deliberately close them or rely on a block that does so automatically.
without using the
Try using "w+" as the write mode instead of just "w" :
OK, now I feel stupid. The first two definitely do not work but the second two do. Not sure how I convinced my self that I had tried them. Sorry for wasting everyone's time.
In case this helps anyone else, this can occur when you are trying to make a new file in a directory that does not exist.
If the objective is just to create a file, the most direct way I see is:
The directory doesn't exist. Make sure it exists as open won't create those dirs for you.
I ran into this myself a while back.
File.new and File.open default to read mode ( 'r' ) as a safety mechanism, to avoid possibly overwriting a file. We have to explicitly tell Ruby to use write mode ( 'w' is the most common way) if we're going to output to the file.
If the text to be output is a string, rather than write:
Use the more succinct write :
write has modes allowed so we can use 'w' , 'a' , 'r+' if necessary.
open with a block is useful if you have to compute the output in an iterative loop and want to leave the file open as you do so. write is useful if you are going to output the content in one blast then close the file.
Файлы в программах играют роль хранилищ, в которые можно записать любые объекты. В отличие от привычных нам объектов, файлы позволяют хранить данные даже тогда, когда программа завершила свою работу. Именно поэтому они могут использоваться для передачи данных между разными программами или разными запусками одной и той же программы.
В своих операционных системах фирма Microsoft ввела понятие «двоичный файл», но оно порождает больше проблем, чем удобств. Особенно при создании кроссплатформенных приложений.
Как организована работа с файлами? В самом общем случае работа с файлами состоит из следующих этапов:
- Открытие файла. Сущность этого этапа состоит в создании объекта класса File .
- Запись или чтение. Вызываются привычные нам методы вывода на экран и не совсем привычные — ввода-вывода.
- Закрытие файла. Во время закрытия файла происходят действия с файловой системой. С объектом, который создаётся при открытии файла, ничего не происходит, но после этого он указывает на закрытый файл, и производить операции чтения/записи при помощи него уже нельзя.
Существует масса способов реализации работы с файлами:
- Чтение при помощи класса IO . Класс IO имеет множество методов, которые позволяют производить чтение из текстовых файлов (с «двоичными файлами» лучше так не работать). Если нужно считать весь текстовый файл, то лучше пользоваться методами класса IO .
- Перенаправление потоков. Существует три предопределённые переменные: $stdout , $stdin и $stderr . Если им присвоить объект класса File (создаваемый во время открытия файла), то весь вывод пойдёт в файл, который присвоили переменной $stdout . Весь ввод будет браться из файла, который присвоили переменной $stdin , а все ошибки будут сохраняться в файле, который присвоили переменной $stderr . Если нужно работать только с одним файлом на чтение и одним файлом на запись, то обычно используют этот способ. Также очень удобно использовать перенаправление потока ошибок (переменная $stderr ) для программ, которые работают в составе пакетных файлов и используют только интерфейс командной строки.
- Универсальный способ. Используется в ситуациях, когда нельзя использовать предыдущие два способа.
Подведём небольшой итог:
- Если нужно считать весь файл целиком, то надо использовать методы класса IO .
- Если нужно работать только с одним файлом на чтение и только одним файлом на запись, то надо использовать перенаправление потока.
- Если нельзя применить два вышеперечисленных способа, то надо использовать универсальный способ работы с файлами.
Для чтения файла целиком используется метод .read . Он считывает весь файл в виде строки. Во время его использования не сто́ит задумываться об открытии/закрытии файла, так как эти операции скрыты внутри метода.
Имя файла — это строка.
В примере можно увидеть, как считывается файл config.yaml в переменную config . Вторая строка показывает, что при использовании метода .read файл считывается в виде строки (класс String ). Теперь к переменной config можно применять любые методы из богатого строкового арсенала.
В качестве упражнения предлагаю вам считывать и выводить на экран любые файлы, которые вы только найдёте у себя на диске.
При считывании «двоичных файлов» в операционных системах фирмы Microsoft использовать данный способ нельзя, так как файл будет считан не до конца. Следует использовать универсальный способ работы с файлами.
Очень часто программист проектирует программу таким образом, чтобы ввод данных осуществлялся с клавиатуры. После сотни-другой циклов отладки программист так устаёт вводить данные, что создаёт файл, который содержит все необходимые входные данные, и перенаправляет на него поток ввода с клавиатуры, добавляя всего одну строчку в начало своей программы:
А вот другая история. Программист пишет и отлаживает программу, которая все необходимые данные выводит на экран. Но в конечном итоге программа должна запускаться без участия человека, и её вывод нужно сохранять в файл для дальнейшей обработки. Переписывать всю программу лень, и поэтому в начало своей программы он вставляет парочку волшебных строчек:
Вторым параметром метода .open передаётся модификатор доступа, то есть кодовое слово, по которому метод .open может предположить то, что вы будете делать с этим файлом. В нашем примере мы использовали модификатор w (англ. write — писать), который говорит о том, что мы будем только писать в файл. Причём каждый раз файл будет перезаписываться. При помощи модификатора a (англ. append — добавлять) мы указываем, что мы будем добавлять данные в файл, а не перезаписывать, как в случае с w .
Метод raise — для принудительного вызова ошибки.
Теперь можете смело экспериментировать. Для начала, попробуйте поменять модификаторы w и a местами.
Универсальным способом я назвал способ с использованием метода File.open . Дело в том, что при помощи него можно осуществлять не только считывание, запись и перезапись, но и закрытие файлов (чего нельзя сделать при использовании способа с переменными $stdout , $stdin и $stderr ). Это позволяет несколько раз (за время выполнения программы) осуществлять операции открытия-закрытия файла. В виду того, что эта возможность нужна далеко не всегда, то и используется этот способ только тогда, когда использование всех предыдущих невозможно. Чтение из файла входные данные.txt при помощи универсального метода будет выглядеть следующим образом:
Модификатор доступа r указывать необязательно, так как он устанавливается по умолчанию. Поэтому следующий код тоже верен:
Если необходимо записать данные, то нужно использовать модификатор доступа a (добавление к концу файла) или w (запись в файл с его предварительной очисткой). Запись данных в файл осуществляется методами puts , write и так далее.
Замыкание метода .open (то есть фигурные скобки) нужен для того, чтобы при выходе из замыкания автоматически осуществлять закрытие файла.
Удаление файла осуществляется при помощи метода delete класса File . Например:
ftools adds several (class, not instance) methods to the File class, for copying, moving, deleting, installing, and comparing files, as well as creating a directory path. See the File class for details. FileUtils contains all or nearly all the same functionality and more, and is a recommended option over ftools When you
then the File class aquires some utility methods for copying, moving, and deleting files, and more. See the method descriptions below, and consider using FileUtils as it is more comprehensive.
ADD, ALT_SEPARATOR, ARCHIVE, BUFSIZE, CHANGE, COMPRESSED, CONTENT_INDEXED, FULL, HIDDEN, INDEXED, MAX_PATH, NORMAL, OFFLINE, PATH_SEPARATOR, READ, READONLY, SECURITY_RIGHTS, SEPARATOR, SYSTEM, Separator, TEMPORARY, VERSION
Методы класса
Методы объекта
Возвращает true если файл или директория являются архивом. Приложения используют этот атрибут для маркировки файлов для резервного копирования или удаления.
Возвращает последнее время доступа для указанного файла как объект Time.
Возвращает массив строк с указанием атрибутов для этого файла. Возможные значения: архив сжатый каталог зашифрованный скрытый индексированный нормальный автономный только чтнение точка повторной обработки разреженная система временная Более одного метода удовлетворяет вашему запросу. Вы можете уточнить ваш запрос, используя метод: File::basename
Возвращает блок файловой системы. Более одного метода удовлетворяет вашему запросу. Вы можете уточнить ваш запрос, используя метод: File::blockdev?
If to is a valid directory, from will be appended to to, adding and escaping backslashes as necessary. Otherwise, to will be returned. Useful for appending from to to only if the filename was not specified in to. Более одного метода удовлетворяет вашему запросу. Вы можете уточнить ваш запрос, используя метод: : File::chardev?
Changes permission bits on files to the bit pattern represented by mode. If the last parameter isn't a String, verbose mode will be enabled.
Changes the owner and group of the named file(s) to the given numeric owner and group id's. Only a process with superuser privileges may change the owner of a file. The current owner of a file may change the file's group to any group to which the owner belongs. A nil or -1 owner or group id is ignored. Returns the number of files processed.
Returns true if and only if the contents of files from and to are identical. If verbose is true, from to is printed.
Returns true if the file or directory is compressed. For a file, this means that all of the data in the file is compressed. For a directory, this means that compression is the default for newly created files and subdirectories.
Returns the change time for the named file (the time at which directory information about the file was changed, not the file itself).
Decrypts an encrypted file or directory. The caller must have the FILE_READ_DATA, FILE_WRITE_DATA, FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access rights. Requires exclusive access to the file being decrypted, and will fail if another process is using the file. If the file is not encrypted an error is NOT raised. Windows 2000 or later only.
Deletes the named files, returning the number of names passed as arguments. Raises an exception on any error. See also Dir::rmdir.
Returns true if the named file is a directory, false otherwise.
Более одного метода удовлетворяет вашему запросу. Вы можете уточнить ваш запрос, выбрав один из следующих методов:
Encrypts a file or directory. All data streams in a file are encrypted. All new files created in an encrypted directory are encrypted. The caller must have the FILE_READ_DATA, FILE_WRITE_DATA, FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access rights. Requires exclusive access to the file being encrypted, and will fail if another process is using the file. If the file is compressed, EncryptFile will decompress the file before encrypting it. Windows 2000 or later only.
Returns true if the file or directory is encrypted. For a file, this means that all data in the file is encrypted. For a directory, this means that encryption is the default for newly created files and subdirectories.
Returns true if the named file is executable by the effective user id of this process.
Returns true if the named file is executable by the real user id of this process.
Return true if the named file exist
Return true if the named file exists. Метод возвращает true , если указанный файл file_name существует
Converts a pathname to an absolute pathname. Relative paths are referenced from the current working directory of the process unless dir_string is given, in which case it will be used as the starting point. The given pathname may start with a ``~, which expands to the process owner's home directory (the environment variable HOME must be set correctly). ``~user expands to the named user's home directory.
Returns the extension (the portion of file name in path after the period).
Returns true if the named file exists and is a regular file.
Returns true if path matches against pattern The pattern is not a regular expression; instead it follows rules similar to shell filename globbing. It may contain the following metacharacters:
Matches any file. Can be restricted by other values in the glob. * will match all files; c* will match all files beginning with c; *c will match all files ending with c; and c will match all files that have c in them (including at the beginning or end). Equivalent to / .* /x in regexp. Matches any one character. Equivalent to /./ in regexp. Matches any one character in set. Behaves exactly like character sets in Regexp, including set negation ([^a-z]).
Escapes the next metacharacter.
flags is a bitwise OR of the FNM_xxx parameters. The same glob pattern and flags are used by Dir::glob.
Returns true if path matches against pattern The pattern is not a regular expression; instead it follows rules similar to shell filename globbing. It may contain the following metacharacters:
Matches any file. Can be restricted by other values in the glob. * will match all files; c* will match all files beginning with c; *c will match all files ending with c; and c will match all files that have c in them (including at the beginning or end). Equivalent to / .* /x in regexp. Matches any one character. Equivalent to /./ in regexp. Matches any one character in set. Behaves exactly like character sets in Regexp, including set negation ([^a-z]).
Escapes the next metacharacter.
flags is a bitwise OR of the FNM_xxx parameters. The same glob pattern and flags are used by Dir::glob.
Identifies the type of the named file; the return string is one of ``file, ``directory, ``characterSpecial, ``blockSpecial, ``fifo, ``link, ``socket, or ``unknown.
Returns a hash describing the current file permissions for the given file. The account name is the key, and the value is an integer representing an or'd value that corresponds to the security permissions for that file. To get a human readable version of the permissions, pass the value to the +File.securities+ method.
Returns true if the named file exists and the effective group id of the calling process is the owner of the file. Returns false on Windows.
Returns true if the file or directory is hidden. It is not included in an ordinary directory listing.
Returns true if the named files are identical.
Returns true if the file or directory is indexed by the content indexing service.
If src is not the same as dest, copies it and changes the permission mode to mode. If dest is a directory, destination is dest/src. If mode is not set, default is used. If verbose is set to true, the name of each file copied will be printed.
Returns a new string formed by joining the strings using File::SEPARATOR.
Equivalent to File::chmod, but does not follow symbolic links (so it will change the permissions associated with the link, not the file referenced by the link). Often not available.
Equivalent to File::chown, but does not follow symbolic links (so it will change the owner associated with the link, not the file referenced by the link). Often not available. Returns number of files in the argument list.
Creates a new name for an existing file using a hard link. Will not overwrite new_name if it already exists (raising a subclass of SystemCallError). Not available on all platforms.
Returns file in long format. For example, if 'SOMEFI~1.TXT' was the argument provided, and the short representation for 'somefile.txt', then this method would return 'somefile.txt'. Note that certain file system optimizations may prevent this method from working as expected. In that case, you will get back the file name in 8.3 format. Более одного метода удовлетворяет вашему запросу. Вы можете уточнить ваш запрос, выбрав один из следующих методов:
Creates a directory and all its parent directories. For example,
causes the following directories to be made, if they do not exist.
You can pass several directories, each as a parameter. If the last parameter isn't a String, verbose mode will be enabled.
Returns the modification time for the named file as a Time object.
Opens the file named by filename according to mode (default is ``r) and returns a new File object. See the description of class IO for a description of mode. The file mode may optionally be specified as a Fixnum by or-ing together the flags (O_RDONLY etc, again described under IO). Optional permission bits may be given in perm. These mode and permission bits are platform dependent; on Unix systems, see open(2) for details.
Returns true if the file or directory has no other attributes set.
Returns true if the data of the file is not immediately available. This attribute indicates that the file data has been physically moved to offline storage. This attribute is used by Remote Storage, the hierarchical storage management software. Applications should not arbitrarily change this attribute.
Returns true if the named file exists and the effective used id of the calling process is the owner of the file.
Returns true if the named file is a pipe.
Returns true if the named file is readable by the effective user id of this process.
Returns true if the named file is readable by the real user id of this process.
Returns the name of the file referenced by the given link. Not available on all platforms.
Returns true if The file or directory is read-only. Applications can read the file but cannot write to it or delete it. In the case of a directory, applications cannot delete it.
Removes the file attributes based on the given (numeric) flags.
Renames the given file to the new name. Raises a SystemCallError if the file cannot be renamed.
Removes a list of files. Each parameter should be the name of the file to delete. If the last parameter isn't a String, verbose mode will be enabled. Returns the number of files deleted.
Returns an array of human-readable strings that correspond to the permission flags.
Sets the file attributes based on the given (numeric) flags. This does not remove existing attributes, it merely adds to them.
Returns true if the named file has the setgid bit set.
Returns true if the named file has the setuid bit set.
Returns 'file_name' in 8.3 format. For example, 'c:\documentation.doc' would be returned as 'c:\docume~1.doc'. Более одного метода удовлетворяет вашему запросу. Вы можете уточнить ваш запрос, выбрав один из следующих методов:
File::size?, File::size, File::blksize, File::size===File::size?===
Returns nil if file_name doesn't exist or has zero size, the size of the file otherwise.
Returns true if the named file is a socket.
File::split, File::splitБолее одного метода удовлетворяет вашему запросу. Вы можете уточнить ваш запрос, выбрав один из следующих методов:
File::lstat, File::stat, File::lstat, File::stat===File::sticky?===
Returns true if the named file has the sticky bit set.
Creates a symbolic link called new_name for the existing file old_name. Raises a NotImplemented exception on platforms that do not support symbolic links.
Returns true if the named file is a symbolic link.
Copies a file from to to. If to is a directory, copies from to to/from.
Returns true if the file or directory is part of the operating system or is used exclusively by the operating system.
Returns true if the file is being used for temporary storage. File systems avoid writing data back to mass storage if sufficient cache memory is available, because often the application deletes the temporary file shortly after the handle is closed. In that case, the system can entirely avoid writing the data. Otherwise, the data will be written after the handle is closed.
Truncates the file file_name to be at most integer bytes long. Not available on all platforms.
Returns the current umask value for this process. If the optional argument is given, set the umask to that value and return the previous value. Umask values are subtracted from the default permissions, so a umask of 0222 would make a file read-only for everyone.
Deletes the named files, returning the number of names passed as arguments. Raises an exception on any error. See also Dir::rmdir.
Sets the access and modification times of each named file to the first two arguments. Returns the number of file names in the argument list.
Returns true if the named file is writable by the effective user id of this process.
Returns true if the named file is writable by the real user id of this process.
Returns true if the named file exists and has a zero size.
Sets whether or not the file is an archive file.
Returns the last access time (a Time object)
(еще известен как o_chmod)
Sets whether or not the file is a compressed file.
Returns the change time for file (that is, the time directory information about the file was changed, not the file itself).
Locks or unlocks a file according to locking_constant (a logical or of the values in the table below). Returns false if File::LOCK_NB is specified and the operation would otherwise have blocked. Not available on all platforms. Locking constants (in class File):
Sets the hidden attribute to true or false. Setting this attribute to true means that the file is not included in an ordinary directory listing.
Sets the 'indexed' attribute to true or false. Setting this to false means that the file will not be indexed by the content indexing service.
(еще известен как content_indexed=)
Returns the modification time for file.
Sets the normal attribute. Note that only 'true' is a valid argument, which has the effect of removing most other attributes. Attempting to pass any value except true will raise an ArgumentError.
Sets whether or not a file is online or not. Setting this to false means that the data of the file is not immediately available. This attribute indicates that the file data has been physically moved to offline storage. This attribute is used by Remote Storage, the hierarchical storage management software. Applications should not arbitrarily change this attribute.
Returns the pathname used to create file as a string. Does not normalize the name.
Sets the readonly attribute. If set to true the the file or directory is readonly. Applications can read the file but cannot write to it or delete it. In the case of a directory, applications cannot delete it.
Sets the file to a sparse (usually image) file. Note that you cannot remove the sparse property from a file.
Set whether or not the file is a system file. A system file is a file that is part of the operating system or is used exclusively by it.
Sets whether or not the file is being used for temporary storage. File systems avoid writing data back to mass storage if sufficient cache memory is available, because often the application deletes the temporary file shortly after the handle is closed. In that case, the system can entirely avoid writing the data. Otherwise, the data will be written after the handle is closed.
Truncates file to at most integer bytes. The file must be opened for writing. Not available on all platforms.
Читайте также: