Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single dash for stdin #1312

Open
ALMaclaine opened this issue Mar 4, 2019 · 14 comments
Open

Single dash for stdin #1312

ALMaclaine opened this issue Mar 4, 2019 · 14 comments

Comments

@ALMaclaine
Copy link

Is it possible to use a single dash as an option, specifically to mark the use of stdin.

ie
echo 123 | cat -

@rchrd2
Copy link

rchrd2 commented Oct 22, 2020

Unfortunately, even inputting - as a string, eg --file="-" produces "" as the value of argv.file.

@rchrd2
Copy link

rchrd2 commented Oct 22, 2020

@ALMaclaine Why did you close it? Is it fixed?

@ALMaclaine
Copy link
Author

Lol because I thought you were a dev and that was a response on why it couldn't happen. Sorry.

@ALMaclaine ALMaclaine reopened this Oct 23, 2020
@bcoe
Copy link
Member

bcoe commented Oct 27, 2020

@ALMaclaine @rchrd2 this isn't a convention I'm accustomed to. Can you point me to some tools that document using - to indicate stdin.

@ALMaclaine
Copy link
Author

@bcoe https://unix.stackexchange.com/questions/16357/usage-of-dash-in-place-of-a-filename

I however, don't remember why I wanted it, I think someone else asked me to implement it in replace.

@rchrd2
Copy link

rchrd2 commented Oct 27, 2020

Here's an example in python:

The argparse module provides a FileType factory which knows about the - convention.

import argparse
p = argparse.ArgumentParser()
p.add_argument("-p", type=argparse.FileType("r"))
args = p.parse_args()

Source: https://stackoverflow.com/a/59381303

Also: For utilities that use operands to represent files to be opened for either reading or writing, the '-' operand should be used to mean only standard input (or standard output when it is clear from context that an output file is being specified) or a file named -. (https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html)

I'm not sure where the convention originated from, but it is widely used.

@buske
Copy link

buske commented Oct 12, 2021

Was just trying to do this and found a solution mentioned here: https://livebook.manning.com/book/node-js-in-action-second-edition/chapter-11/9

It seems like the solution is for the --file option to have nargs = 1 to ensure the - gets parsed as a value rather than another option. For example:

require('yargs')
  .command('import', 'Import the file (or - for stdin)', (yargs) => {
    yargs.option('file', {
      type: 'string',
      nargs: 1,
    })
  }, function (argv) {
    var inputFile = argv.file;
    if (argv.file === '-') {
      inputFile = 0;
    }
    var input = fs.readFileSync(inputFile, 'utf-8');
  })
  .help()
  .argv;

@line-o
Copy link

line-o commented Feb 15, 2022

@buske your comment helped me lot.

I found other useful hints on stdin and would rather not use var anywhere nowadays. So I came up with something along the lines of:

(yargs) => {
    const inputFile = (yargs.file === '-') ? process.stdin.fd : yargs.file;
    // do something with filename, maybe returning it here is the best option
   //  const fileContents = fs.readFileSync(inputFile, 'utf-8');
   return inputFile
}

I ended up doing the replacecement (- by standard input) in the coerce functions.

@shonfeder
Copy link

shonfeder commented Dec 5, 2022

This is a relatively widely used convention in linux/unix world: https://www.baeldung.com/linux/dash-in-command-line-parameters

But, in general, I'd say it's pretty important to allow parsing - as CLI input. Which don't seem able to do at present.

E.g., given a command spec like

const typecheckCmd = {
  command: 'foo <input>',
  desc: 'Check types (TBD) and effects of a TNT specification. Reads from stdin if <input> is -.',
  handler: fooHandler,
}

When I run cmd foo -, the value for yargs.input ends up being true.

This is on yargs@17.6.2

@shadowspawn
Copy link
Member

shadowspawn commented Feb 25, 2023

I know node util.parseArgs and Minimist and Commander all have special case processing to support single dash as an option and argument value. And I think in all three cases it got added after the initial implementation!

Since the reason for special treatment of arguments starting with dashes is for options, and a single dash is not an option, it makes sense to allow it once you know about the convention.

@shadowspawn
Copy link
Member

shadowspawn commented Mar 4, 2023

You can currently use - as an option value or as a positional value for a subcommand by explicitly setting nargs to 1. (It also happens to work without configuration for short options.)

I still think the single dash could be supported by default, but exploring what is possible now.

// options.js
const yargs = require('yargs/yargs')

const result = yargs(process.argv.slice(2))
    .option('input', {alias: 'i', nargs: 1})
    .parse();

console.log(result);
$ node option.js --input INPUT
{ _: [], input: 'INPUT', i: 'INPUT', '$0': 'option.js' }
$ node option.js --input -    
{ _: [], input: '-', i: '-', '$0': 'option.js' }
$ node option.js -i INPUT 
{ _: [], i: 'INPUT', input: 'INPUT', '$0': 'option.js' }
$ node option.js -i -    
{ _: [], i: '-', input: '-', '$0': 'option.js' }
$ node option.js --auto -
{ _: [ '-' ], auto: true, '$0': 'option.js' }
$ node option.js -a -    
{ _: [], a: '-', '$0': 'option.js' }
$ node option.js --auto INPUT
{ _: [], auto: 'INPUT', '$0': 'option.js' }
$ node option.js -a INPUT    
{ _: [], a: 'INPUT', '$0': 'option.js' }
// positional.js
const yargs = require('yargs/yargs')

const result = yargs(process.argv.slice(2))
  .command('run <input>', 'run the server', (yargs) => {
    yargs.nargs('input', 1);
  })
  .command('exec <input>', 'run the server', (yargs) => {
  })
  .parse();
console.log(result);
$ node positional.js exec INPUT
{ _: [ 'exec' ], '$0': 'positional.js', input: 'INPUT' }
$ node positional.js exec -    
{ _: [ 'exec' ], '$0': 'positional.js', input: true }
$ node positional.js run INPUT
{ _: [ 'run' ], '$0': 'positional.js', input: 'INPUT' }
$ node positional.js run -    
{ _: [ 'run' ], '$0': 'positional.js', input: '-' }

@shadowspawn
Copy link
Member

(It also happens to work without configuration for short options.)

No accident, yarns-parser has tests for - for short option and positional, might just be missing the long option support: https://github.com/yargs/yargs-parser/blob/3aba24ceaa1a06ceb982c63a06002526d781e826/test/yargs-parser.cjs#L1489

@shadowspawn
Copy link
Member

I think this will be fixed by yargs/yargs-parser#472

@scriptcoded
Copy link

Sorry for digging up an old issue. I needed to do just this today and discovered a somewhat unexpected and maybe hacky way of implementing this. In my case I'm expecting an array of things, but I would assume this can be modified to work with single arguments as well.

This is written in TypeScript.

import yargs from 'yargs'
import { readFileSync } from 'fs'

function getStdinValues() {
  // If stdin is a TTY ie. it's the user typing then we're don't want to read
  // from stdin as it's most likely not there.
  if (process.stdin.isTTY) {
    return []
  }

  // Read from stdin and split the input by lines.
  const stdin = readFileSync(process.stdin.fd, 'utf-8')
  return stdin
    .trim()
    .split('\n')
    .map(line => line.trim())
    .filter(line => line.length > 0)
}

type Options = {
  things: string[]
}

yargs
  .scriptName('my-script')
  .command<Options>(
    '$0 <things...>',
    'do something with the things',
    () => {},
    (argv) => {
      // Destruct argv so that we don't modify it.
      let { things } = argv
      
      // It appears that if "things" is mandatory but the user passes a single
      // dash (-) it still passes validation but the array is empty. So this is
      // a nice albeit hacky indicator for us to read from stdin instead.
      if (things.length === 0) {
        things = getStdinValues()

        if (things.length === 0) {
          console.error('No things supplied on stdin')
          process.exit(1)
        }
      }

      // "things" is guaranteed to be a non-empty array
      console.log(things)
    })
  .parse()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants