Read from stdin and write to stdout

I am trying to write a filter like bellow in gjs:

#!/bin/bash

while
read line
do
echo $line | tr "B" "b"
done

It will read from stdin, process it, and then write to stdout. This filter will not exit. Run continuously and wait for stdin and give stdout.

So, far what I came up with is:

stdoutStream#!/usr/bin/gjs

#!/usr/bin/gjs

'use strict';

const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;

let loop = GLib.MainLoop.new(null, false);

try {
    let stdoutStream = new Gio.DataInputStream({
        base_stream: new Gio.UnixInputStream({ fd: 0 }),
        close_base_stream: true
    });

    stdoutStream.read_line_async(GLib.PRIORITY_LOW, null, (stdoutStream, res) => {
        try {
            let line = stdoutStream.read_line_finish_utf8(res)[0];

            if (line !== null) {
                print(`READ: ${line}`);
            }
        } catch (e) {
            logError(e);
        } finally {
            // so it definitely quits
            loop.quit();
        }
    });

} catch (e) {
    logError(e);
}

loop.run();

However, this is not acting as a filter.

For example, for the bash example:

$ echo "first
  seconf
  third" | ./filter
first
seconf
third

but for this gjs script:

$ echo "first
  seconf
  third" | ./gjs_test
READ: first

How can I fix this.

read_line_async() reads one line, it’s not called repeatedly for each line. So you need to call it again after processing the previous line (e.g. near the end of the try{} block.
Alternatively, and depending on your needs, you might want to simplify your code not using an async method and call read_line_utf8() repeatedly in a loop. That will depend on what you want to do around the stream reading.

3 Likes

This is exactly what i want.

#!/usr/bin/gjs

'use strict';

const Gio = imports.gi.Gio;


let stdinStream = new Gio.DataInputStream({
    base_stream: new Gio.UnixInputStream({ fd: 0 }),
    close_base_stream: true
});

print(stdinStream.read_line_utf8(null));

There are two issues with this code.

  1. The output looks like:
echo "asdsad
  dfdsfds
  sdfsdf" | ./gjs_test
asdsad,6

Here i do not need the number.
2. Not understanding how to put it in an infinite loop. I surrounded it with while (true) and my prompt filled with ,0.

A little context here.

I have a script which get icon name from file path.

#!/usr/bin/env gjs

const { Gio } = imports.gi;

let file = Gio.File.new_for_path(ARGV[0]);
let fileInfo = file.query_info('*', Gio.FileQueryInfoFlags.NONE, null);
print(fileInfo.get_icon().get_names()[0]);

I have 10,000+ file path so executing this function this many times is slow.

What I want is to use it as a coproc.

#!/bin/bash

coproc get_icon_from_path

while IFS= read -r line; do
    echo "$line" >&"${COPROC[1]}"
    read -r icon <&"${COPROC[0]}"
    echo the icon for $line is $icon
done < <(cat $HOME/file-path-list)
exec {COPROC[1]}>&-

If you’re already using GJS, there’s really no need to use shell scripting. If you are drawing this from the example on gjs.guide, that was really just intended for the purpose of demonstration.

print(stdinStream.read_line_utf8(null));

The return value for Gio.DataInputStream.read_line_utf8() return an array of [String, Number]; the line that was read, and the length of the string. You can deconstruct it if you don’t need the length:

const [line] = stdinStream.read_line_utf8(null);

Your infinite loop is looping, however you are not handling the eventual case of the stream running out of data. It’s returning [null, 0] when there is no more data to be read, and print() is coercing that to a string and printing ,0.

1 Like

Thank you very much for helping me out.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.