teepipe.py 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. #!/usr/bin/env python3
  2. #
  3. # tee, but for pipes
  4. #
  5. # Example:
  6. # ./scripts/tee.py in_pipe out_pipe1 out_pipe2
  7. #
  8. # Copyright (c) 2022, The littlefs authors.
  9. # SPDX-License-Identifier: BSD-3-Clause
  10. #
  11. import os
  12. import io
  13. import time
  14. import sys
  15. def openio(path, mode='r', buffering=-1):
  16. # allow '-' for stdin/stdout
  17. if path == '-':
  18. if mode == 'r':
  19. return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
  20. else:
  21. return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering)
  22. else:
  23. return open(path, mode, buffering)
  24. def main(in_path, out_paths, *, keep_open=False):
  25. out_pipes = [openio(p, 'wb', 0) for p in out_paths]
  26. try:
  27. with openio(in_path, 'rb', 0) as f:
  28. while True:
  29. buf = f.read(io.DEFAULT_BUFFER_SIZE)
  30. if not buf:
  31. if not keep_open:
  32. break
  33. # don't just flood reads
  34. time.sleep(0.1)
  35. continue
  36. for p in out_pipes:
  37. try:
  38. p.write(buf)
  39. except BrokenPipeError:
  40. pass
  41. except FileNotFoundError as e:
  42. print("error: file not found %r" % in_path)
  43. sys.exit(-1)
  44. except KeyboardInterrupt:
  45. pass
  46. if __name__ == "__main__":
  47. import sys
  48. import argparse
  49. parser = argparse.ArgumentParser(
  50. description="tee, but for pipes.",
  51. allow_abbrev=False)
  52. parser.add_argument(
  53. 'in_path',
  54. help="Path to read from.")
  55. parser.add_argument(
  56. 'out_paths',
  57. nargs='+',
  58. help="Path to write to.")
  59. parser.add_argument(
  60. '-k', '--keep-open',
  61. action='store_true',
  62. help="Reopen the pipe on EOF, useful when multiple "
  63. "processes are writing.")
  64. sys.exit(main(**{k: v
  65. for k, v in vars(parser.parse_intermixed_args()).items()
  66. if v is not None}))