diff --git a/unix/file.c b/unix/file.c
index 448cd50edc3e2e05266057966d89b704d43712ed..7f113d6712096099aa526ddda18e91d07c9b5b27 100644
--- a/unix/file.c
+++ b/unix/file.c
@@ -35,6 +35,7 @@
 #include "py/runtime.h"
 #include "py/stream.h"
 #include "py/builtin.h"
+#include "py/mphal.h"
 
 #if MICROPY_PY_IO
 
@@ -80,6 +81,12 @@ STATIC mp_uint_t fdfile_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errc
 STATIC mp_uint_t fdfile_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) {
     mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in);
     check_fd_is_open(o);
+    #if MICROPY_PY_OS_DUPTERM
+    if (o->fd <= STDERR_FILENO) {
+        mp_hal_stdout_tx_strn(buf, size);
+        return size;
+    }
+    #endif
     mp_int_t r = write(o->fd, buf, size);
     if (r == -1) {
         *errcode = errno;
diff --git a/unix/main.c b/unix/main.c
index e7fce873b24917229772bf53ecbe5f4369566ea2..c305c37283d00b6cdecf27201fb3949ab2b9f009 100644
--- a/unix/main.c
+++ b/unix/main.c
@@ -61,6 +61,7 @@ long heap_size = 1024*1024 * (sizeof(mp_uint_t) / 4);
 STATIC void stderr_print_strn(void *env, const char *str, size_t len) {
     (void)env;
     ssize_t dummy = write(STDERR_FILENO, str, len);
+    mp_hal_dupterm_tx_strn(str, len);
     (void)dummy;
 }
 
diff --git a/unix/modos.c b/unix/modos.c
index 26ecdd5b13c6710989556c4fbd90d2675b6157de..f2e83a1643df9e8a7e96febecf33ffed7b910fc9 100644
--- a/unix/modos.c
+++ b/unix/modos.c
@@ -215,6 +215,26 @@ STATIC mp_obj_t mod_os_errno(mp_uint_t n_args, const mp_obj_t *args) {
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_errno_obj, 0, 1, mod_os_errno);
 
+#if MICROPY_PY_OS_DUPTERM
+STATIC mp_obj_t mod_os_dupterm(mp_uint_t n_args, const mp_obj_t *args) {
+    if (n_args == 0) {
+        if (MP_STATE_PORT(term_obj) == MP_OBJ_NULL) {
+            return mp_const_none;
+        } else {
+            return MP_STATE_PORT(term_obj);
+        }
+    } else {
+        if (args[0] == mp_const_none) {
+            MP_STATE_PORT(term_obj) = NULL;
+        } else {
+            MP_STATE_PORT(term_obj) = args[0];
+        }
+        return mp_const_none;
+    }
+}
+MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_dupterm_obj, 0, 1, mod_os_dupterm);
+#endif
+
 STATIC const mp_rom_map_elem_t mp_module_os_globals_table[] = {
     { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos) },
     { MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mod_os_errno_obj) },
@@ -227,6 +247,9 @@ STATIC const mp_rom_map_elem_t mp_module_os_globals_table[] = {
     { MP_ROM_QSTR(MP_QSTR_getenv), MP_ROM_PTR(&mod_os_getenv_obj) },
     { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mod_os_mkdir_obj) },
     { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mod_os_ilistdir_obj) },
+    #if MICROPY_PY_OS_DUPTERM
+    { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mod_os_dupterm_obj) },
+    #endif
 };
 
 STATIC MP_DEFINE_CONST_DICT(mp_module_os_globals, mp_module_os_globals_table);
diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h
index a74345817dc16ae65751fc8608b856a7a70d6965..2061b48950326c777cbe02939910af060314a57c 100644
--- a/unix/mpconfigport.h
+++ b/unix/mpconfigport.h
@@ -210,8 +210,12 @@ void mp_unix_mark_exec(void);
 #define MP_PLAT_ALLOC_EXEC(min_size, ptr, size) mp_unix_alloc_exec(min_size, ptr, size)
 #define MP_PLAT_FREE_EXEC(ptr, size) mp_unix_free_exec(ptr, size)
 
+#if MICROPY_PY_OS_DUPTERM
+#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len)
+#else
 #include <unistd.h>
 #define MP_PLAT_PRINT_STRN(str, len) do { ssize_t ret = write(1, str, len); (void)ret; } while (0)
+#endif
 
 #ifdef __linux__
 // Can access physical memory using /dev/mem
@@ -239,9 +243,19 @@ extern const struct _mp_obj_fun_builtin_t mp_builtin_open_obj;
 
 #define MP_STATE_PORT MP_STATE_VM
 
+#if MICROPY_PY_OS_DUPTERM
+#define ROOT_POINTERS_1 mp_obj_t term_obj
+#include <stddef.h>
+void mp_hal_dupterm_tx_strn(const char *str, size_t len);
+#else
+#define ROOT_POINTERS_1
+#define mp_hal_dupterm_tx_strn(s, l)
+#endif
+
 #define MICROPY_PORT_ROOT_POINTERS \
     const char *readline_hist[50]; \
     mp_obj_t keyboard_interrupt_obj; \
+    ROOT_POINTERS_1; \
     void *mmap_region_head; \
 
 // We need to provide a declaration/definition of alloca()
diff --git a/unix/qstrdefsport.h b/unix/qstrdefsport.h
index d761c0f54126342aa5c1f430c64c10151b6fbe80..167a2be28f7812b3a7281b9993d54b6aaa1fb32f 100644
--- a/unix/qstrdefsport.h
+++ b/unix/qstrdefsport.h
@@ -45,6 +45,9 @@ Q(getenv)
 Q(mkdir)
 Q(ilistdir)
 Q(errno)
+#if MICROPY_PY_OS_DUPTERM
+Q(dupterm)
+#endif
 
 Q(uselect)
 Q(poll)
diff --git a/unix/unix_mphal.c b/unix/unix_mphal.c
index 8d3321ef66d163fb40b664cd98184566b8a4511e..16e80b7ef86d8a9bab0c9126d2c6b558f64fae56 100644
--- a/unix/unix_mphal.c
+++ b/unix/unix_mphal.c
@@ -31,6 +31,7 @@
 
 #include "py/mpstate.h"
 #include "py/mphal.h"
+#include "py/runtime.h"
 
 #ifndef _WIN32
 #include <signal.h>
@@ -106,8 +107,40 @@ void mp_hal_stdio_mode_orig(void) {
 
 #endif
 
+#if MICROPY_PY_OS_DUPTERM
+void mp_hal_dupterm_tx_strn(const char *str, size_t len) {
+    if (MP_STATE_PORT(term_obj) != MP_OBJ_NULL) {
+        mp_obj_t write_m[3];
+        mp_load_method(MP_STATE_PORT(term_obj), MP_QSTR_write, write_m);
+        write_m[2] = mp_obj_new_bytearray_by_ref(len, (char*)str);
+        mp_call_method_n_kw(1, 0, write_m);
+    }
+}
+#endif
+
 int mp_hal_stdin_rx_chr(void) {
     unsigned char c;
+    #if MICROPY_PY_OS_DUPTERM
+    while (MP_STATE_PORT(term_obj) != MP_OBJ_NULL) {
+        mp_obj_t read_m[3];
+        mp_load_method(MP_STATE_PORT(term_obj), MP_QSTR_read, read_m);
+        read_m[2] = MP_OBJ_NEW_SMALL_INT(1);
+        mp_obj_t res = mp_call_method_n_kw(1, 0, read_m);
+        if (res == mp_const_none) {
+            break;
+        }
+        mp_buffer_info_t bufinfo;
+        mp_get_buffer_raise(res, &bufinfo, MP_BUFFER_READ);
+        if (bufinfo.len == 0) {
+            break;
+        }
+        c = *(byte*)bufinfo.buf;
+        if (c == '\n') {
+            c = '\r';
+        }
+        return c;
+    }
+    #endif
     int ret = read(0, &c, 1);
     if (ret == 0) {
         c = 4; // EOF, ctrl-D
@@ -119,6 +152,7 @@ int mp_hal_stdin_rx_chr(void) {
 
 void mp_hal_stdout_tx_strn(const char *str, size_t len) {
     int ret = write(1, str, len);
+    mp_hal_dupterm_tx_strn(str, len);
     (void)ret; // to suppress compiler warning
 }