/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.command.file; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Reader; import java.net.URL; import org.jnode.command.util.IOUtils; import org.jnode.shell.AbstractCommand; import org.jnode.shell.syntax.Argument; import org.jnode.shell.syntax.FileArgument; import org.jnode.shell.syntax.FlagArgument; import org.jnode.shell.syntax.URLArgument; /** * Read files or network resources and write the concatenation to standard output. If * no filenames or URIs are provided, copy standard input to standard output. Data is * copied byte-wise unless flags are used that add additional formatting to the lines. * <p> * If any file or URL cannot be opened, it is skipped and we (eventually) set a non-zero * return code. If we get an IOException reading or writing data, we allow it to propagate. * * @author epr * @author Andreas H\u00e4nel * @author crawley@jnode.org * @author Fabien DUMINY (fduminy@jnode.org) * @author chris boertien */ public class CatCommand extends AbstractCommand { private static final String HELP_FILE = "the files to be concatenated"; private static final String HELP_URL = "the urls to be concatenated"; private static final String HELP_URLS = "If set, arguments will be urls"; private static final String HELP_NUM_NB = "If set, number nonempty output lines"; private static final String HELP_NUM = "If set, number all output lines"; private static final String HELP_ENDS = "If set, print a $ at the end of each lines"; private static final String HELP_SQUEEZE = "If set, supress printing of sequential blank lines"; private static final String HELP_SUPER = "Concatenate the contents of files, urls or standard input"; private static final String ERR_URL = "Cannot fetch %s: %s%n"; private static final String ERR_FILE = "Cannot open %sn"; private final FileArgument argFile; private final FlagArgument argNumNB; private final FlagArgument argNumAll; private final FlagArgument argEnds; private final FlagArgument argSqueeze; private final URLArgument argUrl; private final FlagArgument argUrls; private PrintWriter err; private PrintWriter out; private Reader in; private InputStream stdin; private OutputStream stdout; private File[] files; private String end; private int rc = 0; private int lineCount; private boolean squeeze; private boolean numAll; private boolean numNB; private boolean useStreams; public CatCommand() { super(HELP_SUPER); int fileFlags = Argument.MULTIPLE | Argument.EXISTING | FileArgument.HYPHEN_IS_SPECIAL; argFile = new FileArgument("file", fileFlags, HELP_FILE); argNumNB = new FlagArgument("num-nonblank", 0, HELP_NUM_NB); argNumAll = new FlagArgument("num", 0, HELP_NUM); argEnds = new FlagArgument("show-ends", 0, HELP_ENDS); argSqueeze = new FlagArgument("squeeze", 0, HELP_SQUEEZE); registerArguments(argFile, argNumNB, argNumAll, argEnds, argSqueeze); argUrl = new URLArgument("url", Argument.MULTIPLE | Argument.EXISTING, HELP_URL); argUrls = new FlagArgument("urls", Argument.OPTIONAL, HELP_URLS); registerArguments(argUrl, argUrls); } private static final int BUFFER_SIZE = 8192; public static void main(String[] args) throws Exception { new CatCommand().execute(args); } public void execute() throws IOException { in = getInput().getReader(); stdin = getInput().getInputStream(); out = getOutput().getPrintWriter(); stdout = getOutput().getOutputStream(); err = getError().getPrintWriter(); parseOptions(); if (files != null && files.length > 0) { handleFiles(); out.flush(); exit(rc); } // FIXME remove this url code once wget is more complete URL[] urls = argUrl.getValues(); if (urls != null && urls.length > 0) { byte[] buffer = new byte[BUFFER_SIZE]; for (URL url : urls) { InputStream is = null; try { is = url.openStream(); } catch (IOException ex) { err.format(ERR_URL, url, ex.getLocalizedMessage()); rc = 1; } if (is != null) { try { IOUtils.copyStream(is, stdout, buffer); } finally { IOUtils.close(is); } } } out.flush(); exit(rc); } // should not reach this throw new IllegalStateException("Nothing to process"); } private boolean handleFiles() { InputStream in = null; BufferedReader reader = null; byte[] buffer = new byte[BUFFER_SIZE]; boolean ok = true; for (File file : files) { try { if (useStreams) { if ((in = openFileStream(file)) != null) { IOUtils.copyStream(in, stdout, buffer); } else { rc = 1; } } else { if ((reader = openFileReader(file)) != null) { processReader(reader); } else { rc = 1; } } } catch (IOException e) { rc = 1; } finally { IOUtils.close(in, reader); } } return ok; } /** * Process the input through a BufferedReader. * * Instead of doing a straight stream->stream copy, we process line by line * in order to do some per-line editing before we send to stdout. */ private void processReader(BufferedReader reader) throws IOException { String line; boolean haveBlank = false; while ((line = reader.readLine()) != null) { if (line.length() == 0) { if (!haveBlank) { haveBlank = true; } else if (squeeze) { continue; } } else { haveBlank = false; } if (numAll) { println(line, ++lineCount); } else if (numNB) { println(line, (haveBlank ? -1 : ++lineCount)); } else { println(line, 0); } } } /** * Attempt to open a file, writing an error message on failure. If the file * is '-', return stdin. * * @param file the file to open the stream on * @return the reader, or null */ private InputStream openFileStream(File file) { InputStream ret = null; if (file.getName().equals("-")) { ret = stdin; } else { ret = IOUtils.openInputStream(file); if (ret == null) { err.format(ERR_FILE, file); } } return ret; } /** * Attempt to open a file reader, writing an error message on failure. If the file * is '-', return stdin. * * @param file the file to open the reader on * @return the reader, or null */ private BufferedReader openFileReader(File file) { BufferedReader ret = null; if (file.getName().equals("-")) { ret = new BufferedReader(in, BUFFER_SIZE); } else { ret = IOUtils.openBufferedReader(file, BUFFER_SIZE); if (ret == null) { err.format(ERR_FILE, file); } } return ret; } private void parseOptions() { files = argFile.getValues(); if (files == null || files.length == 0) { files = new File[] {new File("-")}; } useStreams = true; if (argNumNB.isSet()) { numNB = true; useStreams = false; } else if (argNumAll.isSet()) { numAll = true; useStreams = false; } if (argEnds.isSet()) { end = "$"; useStreams = false; } else { end = ""; } if (argSqueeze.isSet()) { squeeze = true; useStreams = false; } } private void println(String s, int line) { if (numNB || numAll) { if (line == -1) { out.format(" %s%s%n", s, end); } else { out.format("%6d %s%s%n", line, s, end); } } else { out.format("%s%s%n", s, end); } } }