2

So I'm trying to find a template for bash/shell script that essentially run a command, let's call it "command1" using an input "X" and then use the output of command1 on itself...but in a loop.

Here a real world example:

echo "hello" | cut -c2-

which will remove the first character at the beginning of the input string and output:

ello

Now, the above is just an example to illustrate the template mentioned above. Following this example, how could i use command1 output:

echo "hello" | cut -c2-

But as input, in a loop, either indefinite loop or until only one byte/character remain.

So that i don't need to copy/paste the output and replace it with the old input:

echo "ello" | cut -c2-

Or need to use multiple pipe which would be too slow/inefficient.

Simpler Explanation

Using manual action, this would be the replacement of me(the user) copy pasting the output of the command i gave as example (or the pseudo code i described earlier) and use that as input for that same command, repeating that same action until "one" byte or char remain.

Nordine Lotfi
  • 2,200
  • 12
  • 45
  • 2
    Is this homework? What have you tried? – Nasir Riley Jan 13 '21 at 17:54
  • 1
    not homework (not in school since a couple years now). This is just for a couple projects/experiment which would be offtopic to this post :) (which is why i called it a template, given i can use this in different ways and contexts) @NasirRiley – Nordine Lotfi Jan 13 '21 at 17:56
  • 1
    What would the final output be of your code? – Kusalananda Jan 13 '21 at 18:07
  • 1
    Please give a pseudo-code example of this loop, I am having a lot of trouble understanding what you mean. You're not just looking for `command | while read line; do echo "$line"; done` are you? What would this loop be looping over? – terdon Jan 13 '21 at 18:08
  • Essentially one byte/character :) @Kusalananda – Nordine Lotfi Jan 13 '21 at 18:08
  • Well, in simple/manual description, this would be the remplacement of me(the user) copy pasting the output of the command i gave as example (or the pseudo code i described earlier) and use that as *input* for that same command, repeating that same action until "one" byte or char remain. :D @terdon – Nordine Lotfi Jan 13 '21 at 18:10
  • 1
    Please [edit] your question and add that since it clarifies your request significantly. – terdon Jan 13 '21 at 18:12
  • 1
    Done :D do feel free to tell me if this need better editing beside this @terdon – Nordine Lotfi Jan 13 '21 at 18:14

4 Answers4

7

If I understand correctly, you are looking for something like this:

$ var=hello
$ while [ -n "$var" ]; do 
    printf -- "Var is now '%s'\n" "$var"
    var=$(printf -- '%s\n' "$var" | cut -c2-); 
done
Var is now 'hello'
Var is now 'ello'
Var is now 'llo'
Var is now 'lo'
Var is now 'o'
terdon
  • 234,489
  • 66
  • 447
  • 667
7

That actually works on Linux:

echo hello | tee /dev/fd/0
hello
hello
hello
...

echo hello | gawk '!length{exit(0)} {print; print substr($0,2) >"/dev/fd/3"; fflush()}' 3>/dev/fd/0
hello
ello
llo
lo
o

That takes advantage of the fact that on Linux all pipes are actually named pipes (i.e. they can be opened via a path). Using a regular named pipe, it will work on other Unix systems, too -- see this answer on Stackoverflow.

  • 1
    this is pretty cool! didn't know pipes could be used like this – Nordine Lotfi Jan 14 '21 at 11:32
  • 1
    @NordineLotfi Strictly speaking, it’s not anything special about pipes here. The magic is all in the `/dev/fd` paths. On (most) Linux systems, they’re special files that allow access to the file descriptors of the current process _in the context they are accessed from_. Not sure about the first example, but the second one works because the shell opens the fd for redirection before running the redirected command, and binds that to the requested fd in the command, which means that the shell’s fd 0 is being bound to fd 3 of the pipeline in the second example. – Austin Hemmelgarn Jan 14 '21 at 23:38
  • @AustinHemmelgarn 1. I don't know what you're trrying to say with "allow access ... in the context they are accessed from" -- I suspect that it's either some banal tautology or something horribly mistaken ;-) –  Jan 15 '21 at 09:10
  • @AustinHemmelgarn 2. The shell opening `/dev/fd/0` has **nothing** to do with it whatsoever, it will work fine with the *original* awk even if `/dev/fd/0` was opened by awk: `echo hello | original-awk '!length{exit(0)} {print; print substr($0,2) >"/dev/fd/0"; fflush()}'`. In that example, I had to do the extra redirection for that silly example because GNU awk (gawk) has a kludge which handles some paths (like `/dev/fd/...`) internally, instead of passing them to the OS, even on OSs which support such paths. –  Jan 15 '21 at 09:13
4

You can do it with recursion:

command1() {
  local var
  IFS= read -r var
  if [[ $var ]]; then
    printf '%s\n' "$var"
    cut -c2- <<< "$var" | "${FUNCNAME[0]}"
  fi
}

Then:

$ echo hello | command1
hello
ello
llo
lo
o
glenn jackman
  • 84,176
  • 15
  • 116
  • 168
3

This seems to work:

foo() { [[ ! "${#1}" -eq 1 ]] && printf "%s" "$1" | cut -c2-; }

bar="hello"

while [[ ! -z $(foo "$bar") ]]; do bar=$(foo "$bar"); printf "%s\n" "$bar"; done

ello
llo
lo
o
schrodingerscatcuriosity
  • 12,087
  • 3
  • 29
  • 57