7 ms·
Improving CLIs with isatty
- mbreese 7y agoI haven’t figured out a way to do this in native Java, so I end up wrapping my Java CLI tools in a bash wrapper (as a self executing JAR). This wrapper’s main purpose is to set a Java property based on the results of the bash test: if [ -t 0 ]; then fi I then use this flag to know if I should do things like export CLI progress bars or other interactive things.
- tedunangst 7y agoAllegedly System.console() but of course it's not actually the same and can be wrong in some important cases, leaving you with jni.
- marvy 7y agowhen would console() be wrong?
- skissane 7y agoSee this code: https://github.com/openjdk/jdk/blob/39c023612dbdd1016ad24b60fdef464bf21d1016/src/java.base/unix/native/libjava/Console_md.c#L38 https://github.com/openjdk/jdk/blob/39c023612dbdd1016ad24b60... It only works when isatty() returns true on both stdin and stdout. If you redirect stdin from a file, but stdout still goes to tty, isatty() on stdout will return true, but System.console() will return null. If you are using isatty() on stdout to decide whether to display fancy output, then whether stdin is a tty or not isn't relevant. So in that scenario System.console() doesn't do what you want.
- marvy 7y agoThat makes sense, thanks! So it's a bit over-cautious.
- skissane 7y agoYou can call isatty() using a Java FFI library such as JNA or JNR. Some day, Java will come with this out-of-the-box and an external library will not be needed – see https://openjdk.java.net/jeps/191 https://openjdk.java.net/jeps/191 For JNA (probably JNR too, my personal experience is only with JNA though), just have to add the JAR to your project (or the Maven/Gradle dependency). The JAR includes native libraries for various platforms. It is non-portable, it won't work on Windows, although you may not care about that, and if you do, Windows has similar APIs (e.g. calling GetConsoleMode() on the file handle will fail if the handle doesn't point to a console.)
- tyingq 7y agoJansi might be worth a look. Provides isatty() and other things that might be helpful for a Java command line app. Works on Windows also. https://github.com/fusesource/jansi https://github.com/fusesource/jansi
- the8472 7y agolinux-specific hack: read /proc/self/fdinfo/1 and check if its mnt_id (/proc/self/mountinfo) corresponds to a devpts mount.
- fao_ 7y agoThe implementation in musl libc is a simple ioctl call[0], so really it should be available in all languages without need for a C FFI. I always find this kind of thing interesting. [0]: https://git.musl-libc.org/cgit/musl/tree/src/unistd/isatty.c https://git.musl-libc.org/cgit/musl/tree/src/unistd/isatty.c
- skissane 7y agoioctl is inherently non-portable. Even though most Unix-like OS implement it, it is not part of the POSIX / SUS standard and so code which relies on it is a portability problem. The problem is the ioctl command codes and their parameters are not standardised and vary from OS to OS. (The POSIX standard does standardise ioctl, but only for use with STREAMS, which is an optional feature which more often than not isn't implemented.)
- cperciva 7y agoJust to be clear, while ioctl has issues, isatty is part of the POSIX standard: https://pubs.opengroup.org/onlinepubs/9699919799/functions/isatty.html https://pubs.opengroup.org/onlinepubs/9699919799/functions/i...
- chrisseaton 7y agoHow would you do an ioctl from Java without an FFI?
- matheusmoreira 7y agoThe JIT compiler could have support for emitting system calls.
- chrisseaton 7y agoThat's not what I meant. I meant there is no ioctl method in the Java standard library. So how do you call it without using some FFI to import the method? You cannot.
- rlpb 7y agoPlease be careful with this. From the GNU Coding Standards: "...please don’t make the behavior of a command-line program depend on the type of output device it gets as standard output or standard input." https://www.gnu.org/prep/standards/standards.html#User-Interfaces https://www.gnu.org/prep/standards/standards.html#User-Inter... Of course you don't have to follow their standards, but their rationale for this particular item is reasonable. I think a warning as described in the article is perhaps just about OK though.
- jankotek 7y agoExactly, this is not an improvement, but makes it worse.
- Normal_gaussian 7y agoOK until, of course, you meet me. And I go around piping things into 'less -RS' I wouldn't get the warning. Though of course getting it to some is probably better than none so I'm more than likely with you. -- Unfortunately many apps do behave differently towards ttys in one key way - colour. It is somewhat frustrating to be piping into less something like 'apt list --upgradable' and being unable go essuly scan because its a mess without colour.
- jancsika 7y agoInstead of a warning it could grep the contents of the current directory. I bet that would work if you were imagining a use case of typing your pipeline on the command line.
- vinc 7y agoDoesn't `ls` behaves like this, printing in multiple columns when the output is a tty?
- majewsky 7y agoIt does, but it can be argued that this is not in response to a certain type of output file. ls(1) formats filename lists into columns depending on the width of the output viewport. It just so happens that it's not able to determine that width for anything but tty devices, so it falls back to the safe default "single column". To use an analogy from web browsers, the equivaleny guideline would be asking websites not to detect features by reading the User-Agent, and using feature detection instead.
- loeg 7y agoisatty on which standard file, though, takes some consideration. Zero, any, some, or all of stdin, stdout, and stderr may be redirected independently. Some programs might usefully print differently redirected into a file vs rendered on a tty. And what is one to do about pipes?
- peterwwillis 7y agoThe problem with doing this is you end up with a bunch of tools which may or may not have unexpected behavior depending on how you ran them. It's better to have a simple design that just works one way, and you're forced to use it in the one right way. When users invariably use it the wrong way, the reasoning about the failures is simpler, and there are no extra bugs from the extra lines of guard-rail code. If you really wanted to add this 'protection code', you could make a bash wrapper around every Unix command that has its input or output connected to a tty. Not perfect, but it would eliminate a whole lot of extra tty-checking code from every app, providing a single reliable interface. I imagine you'd turn it off after a while, though.
- deleted 7y ago[deleted]
- quietbritishjim 7y ago> the isatty function in the C standard library (man 3 isatty) It is not in the C standard library, it's in POSIX so isn't available in non-*nix platforms. The same problem applies in other languages using it through cffi.
- jessaustin 7y agoThere were some interesting suggestions along these lines in the "12 factor CLI" piece previously linked at HN, in sections 6 and 7: https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46#cca8 https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46#...