diff --git a/CHANGELOG.md b/CHANGELOG.md
index 761e9c22ede4900f6845d1cc08ae0e16f28424b5..47ab76d2e8f2e3f89e41cd1cbb2e4f63e6a3b6bc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
 # v2.4.6
 
-* _TBA_
+* added `satella.files`
 
 # v2.4.5
 
diff --git a/docs/files.rst b/docs/files.rst
new file mode 100644
index 0000000000000000000000000000000000000000..35da516bf0d3b71ae86ddbbfaae89ebc2e17f74b
--- /dev/null
+++ b/docs/files.rst
@@ -0,0 +1,7 @@
+File management routines
+========================
+
+read_re_sub_and_write
+---------------------
+
+.. autofunction:: satella.files.read_re_sub_and_write
diff --git a/docs/index.rst b/docs/index.rst
index 222e5ca42b0d12af7966471596e3324269c5c618..2608553ab4f7e40311b3d5de5e918fc48be4f125 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -22,6 +22,7 @@ Visit the project's page at GitHub_!
            json
            posix
            import
+           files
 
 
 Indices and tables
diff --git a/satella/__init__.py b/satella/__init__.py
index 143d8e99b41fdfbd4b3b92fdb518bc0fe410ec00..fdc8312a922c1e01711b7dc10316bd84e75661da 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.4.6rc1'
+__version__ = '2.4.6'
diff --git a/satella/files.py b/satella/files.py
new file mode 100644
index 0000000000000000000000000000000000000000..c24c5113a0ad907d5ccbf5d9369ee423144985cc
--- /dev/null
+++ b/satella/files.py
@@ -0,0 +1,29 @@
+import logging
+import typing as tp
+import re
+
+logger = logging.getLogger(__name__)
+
+__all__ = ['read_re_sub_and_write']
+
+
+def read_re_sub_and_write(path: str, pattern: tp.Union[re.Pattern, str],
+                          repl: tp.Union[tp.Callable[[re.Match], str]]) -> None:
+    """
+    Read a text file, treat with re.sub and write the contents
+
+    :param path: path of file to reat
+    :param pattern: re.Pattern or a string, a pattern to match the file contents
+    :param repl: string or a callable(str)->str to replace the contents
+    """
+    with open(path, 'r') as f_in:
+        data = f_in.read()
+
+    if isinstance(pattern, re.Pattern):
+        data = pattern.sub(repl, data)
+    else:
+        data = re.sub(pattern, repl, data)
+
+    with open(path, 'w') as f_out:
+        f_out.write(data)
+
diff --git a/setup.cfg b/setup.cfg
index 76f466c648dd30e57d74c7243402695cbfcf7c50..11e48984cfe44b007fa63d1e7ba096cebf7929a5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,6 @@
 # coding: utf-8
 [metadata]
 name = satella
-description-file = A set of useful routines and libraries for writing Python long-running services
 long-description = file: README.md
 long-description-content-type = text/markdown; charset=UTF-8
 license_files = LICENSE
diff --git a/tests/test_files.py b/tests/test_files.py
new file mode 100644
index 0000000000000000000000000000000000000000..0353012f06381278df1dd4546da3444b3ac4e86d
--- /dev/null
+++ b/tests/test_files.py
@@ -0,0 +1,18 @@
+import unittest
+import os
+import tempfile
+from satella.files import read_re_sub_and_write
+
+
+class TestFiles(unittest.TestCase):
+    def test_read_re_sub_and_write(self):
+        filename = tempfile.mktemp()
+        with open(filename, 'w') as f_out:
+            f_out.write('RE')
+
+        read_re_sub_and_write(filename, r'RE', 'sub')
+
+        with open(filename, 'r') as f_in:
+            self.assertEqual('sub', f_in.read())
+
+        os.unlink(filename)