Command output redirection works only from console, not from script - bash

I would like to capture command output to variable in bash, but display it as well to console.
exec 5>&1
STATUS=$(zypper info rar|tee >(cat - >&5))
echo $STATUS
It works in console expected way. When calling within following simple script, it works as well expected way.
#!/bin/bash
exec 5>&1
STATUS=$(zypper info rar|tee >(cat - >&5))
echo $STATUS
But when calling within following script, it produces error.
#!/bin/sh
#
# description: currency_trader_tools installation script
# Currency_Trader software.
#
# prerequisities:
# OpenSuse Leap 42.1 x86_64
# clean installation of Minimal Server Selection (Text mode)
# install:
# Midnight Commander - linux file manager
# x11vnc - X11 vnc server
# xvfb-run - X11 virtual frame buffer server
# java - latest JDK environment rpm
#
# commit_id = "0f46a17011ca82c57ddb7f81636984c7bebd5798";
# build_revision_full = "Build 0144 created 2016-05-11 18:04:00 based on commit 0f46a17011ca82c57ddb7f81636984c7bebd5798";
# build_revision_short = "0f46a17";
# build_revision = "0144";
RETVAL=0
ZIP_FILE_VERSIONED="Currency_Trader_Bash_Scripts_0_9_1- r-0144-0f46a17.zip"
ZIP_FILE="Currency_Trader_Bash_Scripts_0_9_1.zip"
# See how we were called.
if [[ ! `whoami` = "root" ]]; then
echo "You must have administrative privileges to run this script"
echo "Try 'sudo ./currency_trader_tools_install'"
exit 1
fi
exec 5>&1
STATUS=$(zypper info rar|tee >(cat - >&5))
echo
echo $STATUS
case "$1" in
all)
install_all
;;
*)
echo $"Usage: currency_trader_tools_install {all}"
exit 1
esac
exit $RETVAL
Error is:
./Currency_Trader_Bash_Scripts_0_9_1-Install-Script: command substitution: line 34: syntax error near unexpected token `('
./Currency_Trader_Bash_Scripts_0_9_1-Install-Script: command substitution: line 34: `zypper info rar|tee >(cat - >&5))'
Any recommendation, how to make the same using sh and not bash?

>(...) is not part of the POSIX standard, so you would need to use an explicit named pipe. However, managing this properly could get tricky. Just capture the output, and output to the console explicitly.
STATUS=$(zypper info rar)
echo "$STATUS"
(The script is already outputting the captured output to the terminal; there doesn't seem to be any need for tee in the first place.)

Related

bash: parse number at the end of a long command

I'm writing a shell script that will run a command and parse out the last few numbers (changes everytime).
Text to parse after running npm run server which outputs:
Please visit http;//mysite.com/id/2318
I want to parse out the value and assign it to id:
2318
My attempt:
id=$(echo npm run server | sed -n 's:.*id\/\(.*\)\n.*:\1:p')
Nothing is being returned.
Addressing your original one-liner:
My attempt:
id=$(echo npm run server | sed -n 's:.*id\/\(.*\)\n.*:\1:p')
Nothing
is being returned.
You could try this instead:
id=$(npm run server | sed -E -e 's:(^.*)(id/)(.*$):\3:g')
NOTE: This addresses only the component of your original attempt that obviously has some workability issues. This doesn't take anything into account except the premise of your quoted output string that you supposedly get from running the command. i.e. I reproduced this using the following command:
echo 'Please visit http;//mysite.com/id/2318' | sed -E -e 's:(^.*)(id/)(.*$):\3:g'
So assuming that when you run npm run server, you get the output 'Please visit http;//mysite.com/id/2318' (which, by the way - I'd suggest might be http: // and not http;//), then this command should return just the id component.
Note that if it's stderr:
If the text your trying to filter is coming out of stderr and not stdout, you may in fact need to use this instead:
id=$(npm run server &> >(sed -E -e 's:(^.*)(id/)(.*$):\3:g'))
For example, parsing the output of an unconfigured npm server:
06:38:23 ✗ :~ >npm run server
npm ERR! Darwin 15.5.006:38:23 ✗ :~ >npm run server | sed -E -e "s/(Darwin)/HELLO/g"
npm ERR! Darwin 15.5.006:38:56 ✗ :~ >npm run server &> >(sed -E -e "s/(Darwin)/HELLO/g")
npm ERR! HELLO 15.5.0
You can see about redirecting stderr in the bash manual:
Redirecting Standard Output and Standard Error
Bash allows both the standard output (file descriptor 1) and the stan-
dard error output (file descriptor 2) to be redirected to the file
whose name is the expansion of word with this construct.
There are two formats for redirecting standard output and standard
error:
&>word
and
>&word
Of the two forms, the first is preferred.
I'm assuming:
That you want to invoke npm run server as a command.
That this command at some point emits the given message on its stdout (as opposed to stderr, direct to the TTY, etc).
That this command does not self-background, and that you want it to keep running even after that output is given.
That it's not important that npm run server continue running after the shell script that started it has exited.
If all those assumptions are correct, consider a process substitution for this job:
#!/usr/bin/env bash
regex='Please visit .*/([[:digit:]]+)$' # define a regex to search for the string
exec 3< <(npm run server) # attach output from "npm run server" to FD 3
## the action is here: searching through output from server until we find a match
while read -r server_output <&3; do # loop reading a line at a time from the server
if [[ $server_output =~ $regex ]]; then # if a line matches the regex...
id=${BASH_REMATCH[1]} # then put the first capture group in a variable
break # and stop looping further.
fi
done
## after-the-fact: log success/failure, and set up any future output to be consumed
## ...so the server doesn't hang trying to write later output/logs to stdout w/ no readers.
if [[ $id ]]; then # if that variable contains a non-empty value
echo "Detected server instance $id" >&2 # log it...
cat </dev/fd/3 >/dev/fd/2 & cat_fd=$! # start a background process to copy any further
# stdout from service to stderr...
exec 3<&- # then close our own copy of the handle.
else
echo "Unable to find an id in stdout of 'npm run server'" >&2
exit 1
fi
## and we're done: if you like, run your other code here.
## also: if you want to wait until the server has exited
## (or at least closed its stdout), consider:
[[ $cat_fd ]] && wait "$cat_fd"

Bash Script works locally but not from server

I have a script which runs fine when executed locally on a mac.
# Ask to make system changes
read -p "Would You Like to make system changes? (Finder, etc...) ? (y/n)?" CONT
if [ "$CONT" == "y" ]; then
# Keep-alive: update existing `sudo` time stamp until `mac.sh` has finished
echo "Keep this mac alive while ding this task..."
while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null &
# Show Date on menubar
echo "Showing Date on menubar..."
defaults write com.apple.menuextra.clock.plist DateFormat "EEE dd MMM h:mm:ss a"
killall "SystemUIServer";
else
echo "Done makng system changes"
fi
But when I try remotely like this:
curl -s https://192.168.63.23/mac.sh --insecure | sh
I get this error:
sh: line 93: syntax error near unexpected token `else'
sh: line 93: `else'
You aren't executing anything remotely; you are just copying the script from a remote server to execute locally. One problem is that your script uses bash extensions like read -p, but you are executing it with sh (which, even if it is a link to bash, executes in POSIX mode). Use bash explicitly:
curl -s --insecure https:/192.168.63.23/mac.sh | bash
As for the syntax error, run your code through shellcheck.net. You haven't shown enough of the actual code to locate the problem.

Display results of specific commands in bash script to screen

In my script I am executing some git commands but I would like the output of the commands to display on the screen as the script runs.
OS X Yosemite, if that matters.
#!/bin/sh
# get the Git command from parameter
if [ $# -eq 0 ];
then
echo "no arguments supplied"
exit 1
fi
cmd=$1
echo "doing some stuff"
# do some stuff (not echoed to screen)
echo "executing command"
# this is the command I want to echo output to screen
git $cmd
echo "doing some other"
# do other stuff (not echoed to screen)
The stuff you don't want echoed to the screen can be redirected to /dev/null like so:
ls /tmp > /dev/null
The results of your git command will be echoed to the screen unless you've specifically said otherwise.
Adding set -x at the start of your script will print the commands before execution.
Example:
#!/bin/sh
set -x
# get the Git command from parameter
if [ $# -eq 0 ];
then
echo "no arguments supplied"
exit 1
fi
# ...

Bash: redirect to screen or /dev/null depending on flag

I'm trying to come up with a way script to pass a silent flag in a bash so that all output will be directed to /dev/null if it is present and to the screen if it is not.
An MWE of my script would be:
#!/bin/bash
# Check if silent flag is on.
if [ $2 = "-s" ]; then
echo "Silent mode."
# Non-working line.
out_var = "to screen"
else
echo $1
# Non-working line.
out_var = "/dev/null"
fi
command1 > out_var
command2 > out_var
echo "End."
I call the script with two variables, the first one is irrelevant and the second one ($2) is the actual silent flag (-s):
./myscript.sh first_variable -s
Obviously the out_var lines don't work, but they give an idea of what I want: a way to direct the output of command1 and command2 to either the screen or to /dev/null depending on -s being present or not.
How could I do this?
You can use the naked exec command to redirect the current program without starting a new one.
Hence, a -s flag could be processed with something like:
if [[ "$1" == "-s" ]] ; then
exec >/dev/null 2>&1
fi
The following complete script shows how to do it:
#!/bin/bash
echo XYZZY
if [[ "$1" == "-s" ]] ; then
exec >/dev/null 2>&1
fi
echo PLUGH
If you run it with -s, you get XYZZY but no PLUGH output (well, technically, you do get PLUGH output but it's sent to the /dev/null bit bucket).
If you run it without -s, you get both lines.
The before and after echo statements show that exec is acting as described, simply changing redirection for the current program rather than attempting to re-execute it.
As an aside, I've assumed you meant "to screen" to be "to the current standard output", which may or may not be the actual terminal device (for example if it's already been redirected to somewhere else). If you do want the actual terminal device, it can still be done (using /dev/tty for example) but that would be an unusual requirement.
There are lots of things that could be wrong with your script; I won't attempt to guess since you didn't post any actual output or errors.
However, there are a couple of things that can help:
You need to figure out where your output is really going. Standard output and standard error are two different things, and redirecting one doesn't necessarily redirect the other.
In Bash, you can send output to /dev/stdout or /dev/stderr, so you might want to try something like:
# Send standard output to the tty/pty, or wherever stdout is currently going.
cmd > /dev/stdout
# Do the same thing, but with standard error instead.
cmd > /dev/stderr
Redirect standard error to standard output, and then send standard output to /dev/null. Order matters here.
cmd 2>&1 > /dev/null
There may be other problems with your script, too, but for issues with Bash shell redirections the GNU Bash manual is the canonical source of information. Hope it helps!
If you don't want to redirect all output from your script, you can use eval. For example:
$ fd=1
$ eval "echo hi >$a" >/dev/null
$ fd=2
$ eval "echo hi >$a" >/dev/null
hi
Make sure you use double quotes so that the variable is replaced before eval evaluates it.
In your case, you just needed to change out_var = "to screen" to out_var = "/dev/tty". And use it like this command1 > $out_var (see the '$' you are lacking)
I implemented it like this
# Set debug flag as desired
DEBUG=1
# DEBUG=0
if [ "$DEBUG" -eq "1" ]; then
OUT='/dev/tty'
else
OUT='/dev/null'
fi
# actual script use commands like this
command > $OUT 2>&1
# or like this if you need
command 2> $OUT
Of course you can also set the debug mode from a cli option, see How do I parse command line arguments in Bash?
And you can have multiple debug or verbose levels like this
# Set VERBOSE level as desired
# VERBOSE=0
VERBOSE=1
# VERBOSE=2
VERBOSE1='/dev/null'
VERBOSE2='/dev/null'
if [ "$VERBOSE" -gte 1 ]; then
VERBOSE1='/dev/tty'
fi
if [ "$VERBOSE" -gte 2 ]; then
VERBOSE2='/dev/tty'
fi
# actual script use commands like this
command > $VERBOSE1 2>&1
# or like this if you need
command 2> $VERBOSE2

Simple bash script for starting application silently

Here I am again. Today I wrote a little script that is supposed to start an application silently in my debian env.
Easy as
silent "npm search 1234556"
This works but not at all.
As you can see, I commented the section where I have some troubles.
This line:
$($cmdLine) &
doesn't hide application output but this one
$($1 >/dev/null 2>/dev/null) &
works perfectly. What am I missing? Many thanks.
#!/bin/sh
# Daniele Brugnara
# October, 2013
# Silently exec a command line passed as argument
errorsRedirect=""
if [ -z "$1" ]; then
echo "Please, don't joke me..."
exit 1
fi
cmdLine="$1 >/dev/null"
# if passed a second parameter, errors will be hidden
if [ -n "$2" ]; then
cmdLine="$cmdLine 2>/dev/null"
fi
# not working
$($cmdLine) &
# works perfectly
#$($1 >/dev/null 2>/dev/null) &
With the use of evil eval following script will work:
#!/bin/sh
# Silently exec a command line passed as argument
errorsRedirect=""
if [ -z "$1" ]; then
echo "Please, don't joke me..."
exit 1
fi
cmdLine="$1 >/dev/null"
# if passed a second parameter, errors will be hidden
if [ -n "$2" ]; then
cmdLine="$cmdLine 2>&1"
fi
eval "$cmdLine &"
Rather than building up a command with redirection tacked on the end, you can incrementally apply it:
#!/bin/sh
if [ -z "$1" ]; then
exit
fi
exec >/dev/null
if [ -n "$2" ]; then
exec 2>&1
fi
exec $1
This first redirects stdout of the shell script to /dev/null. If the second argument is given, it redirects stderr of the shell script too. Then it runs the command which will inherit stdout and stderr from the script.
I removed the ampersand (&) since being silent has nothing to do with running in the background. You can add it back (and remove the exec on the last line) if it is what you want.
I added exec at the end as it is slightly more efficient. Since it is the end of the shell script, there is nothing left to do, so you may as well be done with it, hence exec.
& means that you're doing sort of multitask whereas
1 >/dev/null 2>/dev/null
means that you redirect the output to a sort of garbage and that's why you don't see anything.
Furthermore cmdLine="$1 >/dev/null" is incorrect, you should use ' instead of " :
cmdLine='$1 >/dev/null'
you can build your command line in a var and run a bash with it in background:
bash -c "$cmdLine"&
Note that it might be useful to store the output (out/err) of the program, instead of trow them in null.
In addition, why do you need errorsRedirect??
You can even add a wait at the end, just to be safe...if you want...
#!/bin/sh
# Daniele Brugnara
# October, 2013
# Silently exec a command line passed as argument
[ ! $1 ] && echo "Please, don't joke me..." && exit 1
cmdLine="$1>/dev/null"
# if passed a second parameter, errors will be hidden
[ $2 ] && cmdLine+=" 2>/dev/null"
# not working
echo "Running \"$cmdLine\""
bash -c "$cmdLine" &
wait

Resources