bash prepend text to every line printed by commands - bash

I'm trying to find a way to do something like this:
# script.sh:
cmd0
set_prepend "some text"
cmd1
cmd2
cmd3
unset_prepend
cmd4
Then whatever stdout generated by cmd1, 2 and 3, every line will be prepended by "some text". There is no relationship between the commands, and the commands can be anything (ls, cat, awk, whatever):
$ script.sh
cmd0 line1
...
cmd0 lineN0
some text cmd1 line1
some text ...
some text cmd1 lineN1
some text cmd2 line1
some text ...
some text cmd2 lineN2
some text cmd3 line1
some text ...
some text cmd3 lineN3
cmd4 line1
...
cmd4 lineN4
The only way I can think of is far from elegant:
script.sh | prepender
and for each line received by prepender, it checks the existence of a file; if the file exists, the contents are the text to prepend; and set_prepend would create that file and unset_prepend would remove it. However buffering would interfere with this, so it would have to be turned off, and I'm not sure how to garantee that a line going to stdout will be processed by prepender before the next script cmd is execute (otherwise race condition possible).

Use exec to redirect output to a pipe. You'll need to save the old stdout in another FD so you can restore it later.
set_prepend() {
exec 3>&1 | sed "s/^/$1 /"
}
unset_prepend() {
exec >&3 3>&-
}

Related

Capture /dev/tty in sub sub shell

I have tow bash files.
File Child
echo some text | tee /dev/tty
File Parent
my_variable=$(./Child)
Executing ./Child prints "some text", executing ./Parent prints "some text" twice. So far so good.
However, if I modify Child file to:
a=$(echo some text | tee /dev/tty)
Then Child still prints "some text" but Parent only prints once, and my_variable is an empty variable.
Is there a way to capture the value from /dev/tty on the parent?

Bash alias or function to create multiline file

I have the following bash command that creates a multiline file in the current directory:
cat > data.txt <<EOL
Line 1
Another line
Something else
EOL
I'm trying to create a bash alias or function to run this command. I've tried the following but nothing happens:
alias create-file="
cat > ~/Desktop/stuffs/data.txt <<EOL
Line 1
Hello world
Another line
EOL"
No luck trying the funciton either:
function npmrcpersonal() {
"cat > ~/Desktop/stuffs/data.txt <<EOL
Line 1
Hello world
Another line
EOL"
}
What am I doing wrong here?
You don't quote the body of a function. And the here-doc shouldn't be indented.
function npmrcpersonal() {
cat > ~/Desktop/stuffs/data.txt <<EOL
Line 1
Hello world
Another line
EOL
}
The EOL marker will only be recognized if it's at the left margin (unless you use <<-EOL, then it's allowed to be indented, but only with TAB characters, not spaces). The rest of the here-doc shouldn't be indented because those spaces will go into the file, and you probably don't want that.

How to zip stdin along with stdout line by line

I have a simple command (my_cc) that computes the number of characters in each line.
This command yields 5, 6, 7, and 8 for text file respectively.
$ cat text
12345
123456
1234567
12345678
$ cat text | ./my_cc
5
6
7
8
My question is how to zip stdin along with stdout line by line like (without multiple processes):
$ cat text | some_magic_command with my_cc
12345 5
123456 6
1234567 7
12345678 8
A possible answer is:
$ cat text | xargs -I {} bash -c "echo {} | ./my_cc | sed 's/^/{} /g'"
12345 5
123456 6
1234567 7
12345678 8
But this invokes processes of my_cc as the number of lines in text.
I can not use this command because my_cc is too heavy to run for each line. Also I can not modify the my_cc.
You can use paste:
paste -d ' ' text <(./my_cc < text)
This puts a space between each line of text and the output of your command.
If you have a shell that doesn't support process substitution, you can read from standard input instead:
./my_cc < text | paste -d ' ' text -
If
my_cc doesn't buffer its output, but writes a line of output immediately after receiving each line of input (most commands don't do that), and
your text doesn't come from a file but is e.g. generated from another command on the fly,
you can do the following:
my_cc() {
perl -nle 'BEGIN { $| = 1 } print length'
}
coproc my_cc
while read -r; do
printf '%s ' "$REPLY"
printf '%s\n' "$REPLY" >&${COPROC[1]}
read -r <&${COPROC[0]}
printf '%s\n' "$REPLY"
done < <( echo '12345
123456
.
1234567
12345678' )
exec {COPROC[0]}<&- {COPROC[1]}>&-
wait $COPROC_PID
Output:
12345 5
123456 6
. 5
1234567 7
12345678 8
Note:
Condition #1 is essential. If my_cc buffers its output, this code will deadlock.
Condition #2 is not strictly required. You could easily run this code on a file (while read -r; do ... done < sometextfile), but a file can be read multiple times, so simpler solutions (that don't require condition #1) are possible.
Explanation:
my_cc is defined as a shell function to stand in for your actual command. It does what you described (prints the length of each line), but $| = 1 deserves comment: This statement enables autoflush mode on the currently selected output handle (which defaults to stdout), i.e. output is written immediately after each print command.
coproc is a bash built-in command that runs the specified command in the background (as a co-process).
The while read -r loop reads input line by line from another command (here played by echo '...').
Each line read ($REPLY) is first printed followed by a space, then sent to the coprocess.
Then we read a single line of output from the coprocess and print it followed by a newline.
At the end we close the file descriptors of our coprocess and wait for it to terminate.

sed to grep lines after specific line for further processing

I am working with a script which looks for file lines after a specific line and process them to get data from it.
Let me illustrate with an example,
if file "sample.log" has lines like
qwerty asdf foo bar
foo
time: 1:00 PM
foo1 bar1
foo foo fooo copying file abc/def/ghi/foo.txt
bar bar1 bar2 copying file efg/qwe/bar.txt
foo
My script should search for contents after time: 1:00 PM. After finding those lines, it must look for lines matching the pattern "copying" and get the path specified in the line.
In this case, output written to another file should be
abc/def/ghi/foo.txt
efg/qwe/bar.txt
I tried this using following command but getting empty string as output. Please guide me with this
sed -n '/^time: 1:00 PM/{/^(.*)copying file/s/^(.*)copying file //p}' ../../sample.log
If you're already in Tcl, you could code it in Tcl:
set fid [open "FILE" r]
set have_time false
while {[gets $fid line] != -1} {
if {$have_time && [regexp {copying file (.*)} $line -> filename]} {
puts $filename
} elseif {[string first "time:" $line] > -1} {
set have_time true
}
}
close $fid
If your file is quite huge, exec sed may be faster, but you'll have to see for yourself.
Note, if you're going to exec sed, keep in mind that inside Tcl, single quotes have no special meaning: use braces to quote the sed program.
exec sed -e {do stuff here} FILE
sed '/1:00 PM/,$ {/copying/s:.*file \(.*\):\1:p};d' FILE
This might work for you (GNU sed):
sed -ne '/1:00 PM/,$!b' -e 's/.*copying.* //w copy' file

How to concatenate stdin and a string?

How to I concatenate stdin to a string, like this?
echo "input" | COMMAND "string"
and get
inputstring
A bit hacky, but this might be the shortest way to do what you asked in the question (use a pipe to accept stdout from echo "input" as stdin to another process / command:
echo "input" | awk '{print $1"string"}'
Output:
inputstring
What task are you exactly trying to accomplish? More context can get you more direction on a better solution.
Update - responding to comment:
#NoamRoss
The more idiomatic way of doing what you want is then:
echo 'http://dx.doi.org/'"$(pbpaste)"
The $(...) syntax is called command substitution. In short, it executes the commands enclosed in a new subshell, and substitutes the its stdout output to where the $(...) was invoked in the parent shell. So you would get, in effect:
echo 'http://dx.doi.org/'"rsif.2012.0125"
use cat - to read from stdin, and put it in $() to throw away the trailing newline
echo input | COMMAND "$(cat -)string"
However why don't you drop the pipe and grab the output of the left side in a command substitution:
COMMAND "$(echo input)string"
I'm often using pipes, so this tends to be an easy way to prefix and suffix stdin:
echo -n "my standard in" | cat <(echo -n "prefix... ") - <(echo " ...suffix")
prefix... my standard in ...suffix
There are some ways of accomplish this, i personally think the best is:
echo input | while read line; do echo $line string; done
Another can be by substituting "$" (end of line character) with "string" in a sed command:
echo input | sed "s/$/ string/g"
Why i prefer the former? Because it concatenates a string to stdin instantly, for example with the following command:
(echo input_one ;sleep 5; echo input_two ) | while read line; do echo $line string; done
you get immediatly the first output:
input_one string
and then after 5 seconds you get the other echo:
input_two string
On the other hand using "sed" first it performs all the content of the parenthesis and then it gives it to "sed", so the command
(echo input_one ;sleep 5; echo input_two ) | sed "s/$/ string/g"
will output both the lines
input_one string
input_two string
after 5 seconds.
This can be very useful in cases you are performing calls to functions which takes a long time to complete and want to be continuously updated about the output of the function.
You can do it with sed:
seq 5 | sed '$a\6'
seq 5 | sed '$ s/.*/\0 6/'
In your example:
echo input | sed 's/.*/\0string/'
I know this is a few years late, but you can accomplish this with the xargs -J option:
echo "input" | xargs -J "%" echo "%" "string"
And since it is xargs, you can do this on multiple lines of a file at once. If the file 'names' has three lines, like:
Adam
Bob
Charlie
You could do:
cat names | xargs -n 1 -J "%" echo "I like" "%" "because he is nice"
Also works:
seq -w 0 100 | xargs -I {} echo "string "{}
Will generate strings like:
string 000
string 001
string 002
string 003
string 004
...
The command you posted would take the string "input" use it as COMMAND's stdin stream, which would not produce the results you are looking for unless COMMAND first printed out the contents of its stdin and then printed out its command line arguments.
It seems like what you want to do is more close to command substitution.
http://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html#Command-Substitution
With command substitution you can have a commandline like this:
echo input `COMMAND "string"`
This will first evaluate COMMAND with "string" as input, and then expand the results of that commands execution onto a line, replacing what's between the ‘`’ characters.
cat will be my choice: ls | cat - <(echo new line)
With perl
echo "input" | perl -ne 'print "prefix $_"'
Output:
prefix input
A solution using sd (basically a modern sed; much easier to use IMO):
# replace '$' (end of string marker) with 'Ipsum'
# the `e` flag disables multi-line matching (treats all lines as one)
$ echo "Lorem" | sd --flags e '$' 'Ipsum'
Lorem
Ipsum#no new line here
You might observe that Ipsum appears on a new line, and the output is missing a \n. The reason is echo's output ends in a \n, and you didn't tell sd to add a new \n. sd is technically correct because it's doing exactly what you are asking it to do and nothing else.
However this may not be what you want, so instead you can do this:
# replace '\n$' (new line, immediately followed by end of string) by 'Ipsum\n'
# don't forget to re-add the `\n` that you removed (if you want it)
$ echo "Lorem" | sd --flags e '\n$' 'Ipsum\n'
LoremIpsum
If you have a multi-line string, but you want to append to the end of each individual line:
$ ls
foo bar baz
$ ls | sd '\n' '/file\n'
bar/file
baz/file
foo/file
I want to prepend my sql script with "set" statement before running it.
So I echo the "set" instruction, then pipe it to cat. Command cat takes two parameters : STDIN marked as "-" and my sql file, cat joins both of them to one output. Next I pass the result to mysql command to run it as a script.
echo "set #ZERO_PRODUCTS_DISPLAY='$ZERO_PRODUCTS_DISPLAY';" | cat - sql/test_parameter.sql | mysql
p.s. mysql login and password stored in .my.cnf file

Resources