in gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java [577:783]
protected void tail(CommandSession session, Process process, String[] argv) throws Exception {
String[] usage = {
"tail - displays last lines of file",
"Usage: tail [-f] [-q] [-c # | -n #] [file ...]",
" -? --help Show help",
" -q --quiet Suppress headers when printing multiple sources",
" -f --follow Do not stop at end of file",
" -F --FOLLOW Follow and check for file renaming or rotation",
" -n --lines=LINES Number of lines to print",
" -c --bytes=BYTES Number of bytes to print",
};
Options opt = parseOptions(session, usage, argv);
if (opt.isSet("lines") && opt.isSet("bytes")) {
throw new IllegalArgumentException("usage: tail [-f] [-q] [-c # | -n #] [file ...]");
}
int lines;
int bytes;
if (opt.isSet("lines")) {
lines = opt.getNumber("lines");
bytes = Integer.MAX_VALUE;
} else if (opt.isSet("bytes")) {
lines = Integer.MAX_VALUE;
bytes = opt.getNumber("bytes");
} else {
lines = 10;
bytes = Integer.MAX_VALUE;
}
boolean follow = opt.isSet("follow") || opt.isSet("FOLLOW");
AtomicReference<Object> lastPrinted = new AtomicReference<>();
WatchService watchService = follow ? session.currentDir().getFileSystem().newWatchService() : null;
Set<Path> watched = new HashSet<>();
class Input implements Closeable {
String name;
Path path;
Reader reader;
StringBuilder buffer;
long ino;
long size;
public Input(String name) {
this.name = name;
this.buffer = new StringBuilder();
}
public void open() {
if (reader == null) {
try {
InputStream is;
if ("-".equals(name)) {
is = new StdInSource(process).read();
} else {
path = session.currentDir().resolve(name);
is = Files.newInputStream(path);
if (opt.isSet("FOLLOW")) {
try {
ino = (Long) Files.getAttribute(path, "unix:ino");
} catch (Exception e) {
// Ignore
}
}
size = Files.size(path);
}
reader = new InputStreamReader(is);
} catch (IOException e) {
// Ignore
}
}
}
@Override
public void close() throws IOException {
if (reader != null) {
try {
reader.close();
} finally {
reader = null;
}
}
}
public boolean tail() throws IOException {
open();
if (reader != null) {
if (buffer != null) {
char[] buf = new char[1024];
int nb;
while ((nb = reader.read(buf)) > 0) {
buffer.append(buf, 0, nb);
if (bytes > 0 && buffer.length() > bytes) {
buffer.delete(0, buffer.length() - bytes);
} else {
int l = 0;
int i = -1;
while ((i = buffer.indexOf("\n", i + 1)) >= 0) {
l++;
}
if (l > lines) {
i = -1;
l = l - lines;
while (--l >= 0) {
i = buffer.indexOf("\n", i + 1);
}
buffer.delete(0, i + 1);
}
}
}
String toPrint = buffer.toString();
print(toPrint);
buffer = null;
if (follow && path != null) {
Path parent = path.getParent();
if (!watched.contains(parent)) {
parent.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
watched.add(parent);
}
}
return follow;
}
else if (follow && path != null) {
while (true) {
long newSize = Files.size(path);
if (size != newSize) {
char[] buf = new char[1024];
int nb;
while ((nb = reader.read(buf)) > 0) {
print(new String(buf, 0, nb));
}
size = newSize;
}
if (opt.isSet("FOLLOW")) {
long newIno = 0;
try {
newIno = (Long) Files.getAttribute(path, "unix:ino");
} catch (Exception e) {
// Ignore
}
if (ino != newIno) {
close();
open();
ino = newIno;
size = -1;
continue;
}
}
break;
}
return true;
} else {
return false;
}
} else {
Path parent = path.getParent();
if (!watched.contains(parent)) {
parent.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
watched.add(parent);
}
return true;
}
}
private void print(String toPrint) {
if (lastPrinted.get() != this && opt.args().size() > 1 && !opt.isSet("quiet")) {
process.out().println();
process.out().println("==> " + name + " <==");
}
process.out().print(toPrint);
lastPrinted.set(this);
}
}
if (opt.args().isEmpty()) {
opt.args().add("-");
}
List<Input> inputs = new ArrayList<>();
for (String name : opt.args()) {
Input input = new Input(name);
inputs.add(input);
}
try {
boolean cont = true;
while (cont) {
cont = false;
for (Input input : inputs) {
cont |= input.tail();
}
if (cont) {
WatchKey key = watchService.take();
key.pollEvents();
key.reset();
}
}
} catch (InterruptedException e) {
// Ignore, this is the only way to quit
} finally {
for (Input input : inputs) {
input.close();
}
}
}