Is there a way for me to get specific words from an output of a command in bash? - bash

For example, when running the dpkg command,
dpkg -s autofs
I would get an output like
dpkg-query: package 'autofs' is not installed and no information is available
But I just want to get the
not installed
part so that I can further use it for my script. Is there a command that can help me with it?

First of all, the words not installed are locale specific, so it will fail with anything but English locales. It is also non-predictable because it is not a published API Application Programming Interface.
So, even with some precautions, do not use this:
LC_MESSAGES=C dpkg-query --status autofs 2>&1 | grep -o 'not installed'
Check the return status of the dpkg-query command instead:
#!/usr/bin/env sh
package='autofs'
if dpkg-query --status "$package" >/dev/null 2>&1; then
printf 'Package %s is installed!\n' "$package"
else
printf 'Package %s is not installed!\n' "$package"
fi

Related

Bash. Parse error output without showing error

I want to get and parse the python (python2) version. This way (which works):
python2 -V 2>&1 | sed 's/.* \([0-9]\).\([0-9]\).*/\1\2/'
For some reason, python2 is showing the version using the -V argument on its error output. Because this is doing nothing:
python2 -V | sed 's/.* \([0-9]\).\([0-9]\).*/\1\2/'
So it needs to be redirected 2>&1 to get parsed (stderr to stdout). Ok, but I'd like to avoid the error shown if a user launching this command has no python2 installed. The desired output on screen for a user who not have python2 installed is nothing. How can I do that? because I need the error output shown to parse the version.
I already did a solution doing before a conditional if statement using the hash command to know if the python2 command is present or not... so I have a working workaround which avoids the possibility of launching the python2 command if it is not present... but just curiosity. Forget about python2. Let's suppose is any other command which is redirecting stderr to stdout. Is there a possibility (bash trick) to parse its output without showing it if there is an error?
Any idea?
Print output only if the line starts with Python 2:
python2 -V 2>&1 | sed -n 's/^Python 2\.\([0-9]*\).*/2\1/p'
or,
command -v python2 >/dev/null && python2 -V 2>&1 | sed ...
Include the next line in your script
command python2 >/dev/null 2>&1 || {echo "python2 not installed or in PATH"; exit 1; }
EDITED: Changed which into command

how do i print out rpm sudo --version in bash script

#!/bin/bash
if rpm -q sudo > /dev/null; then
echo "sudo is installed and version is"
else
echo "sudo is not installed"
fi
hi was wondering if it is possible to print out my sudo version after checking that it is installed. If there's a way, how can I do it
sudo --version output must be parsed and the return status used to determine if it is installed.
First of all, you want the version output to be consistent, regardless of active system locale.
So you invoke it with LANG=C sudo --version.
This prints multiple lines and only the first line last element is the sudo version:
Sudo version 1.9.5p2
Sudoers policy plugin version 1.9.5p2
Sudoers file grammar version 48
Sudoers I/O plugin version 1.9.5p2
Sudoers audit plugin version 1.9.5p2
Using POSIX-shell grammer, lines and field parsing must be done with external tools. Here awk is capable of parsing it all at once:
#!/usr/bin/env sh
# Captures version string parsed with awk:
# so only last entry of first line is captured
if sudo_version=$(LANG=C sudo --version 2>/dev/null | awk 'NR==1{print $NF}')
then printf 'sudo is installed and version is %s\n' "$sudo_version"
else printf 'sudo is not installed\n' >&2
fi
If using Bash, capturing the first line into an array save from using external tools and pipe forking process:
#!/usr/bin/env bash
# Capture version string first line into a Bash array
if read -ra version_array < <(LANG=C sudo --version 2>/dev/null)
then
# Version is the last element of array
printf 'sudo is installed and version is %s\n' "${version_array[-1]}"
else
printf 'sudo is not installed\n' >&2
fi

Create and write systemd service from Shell script Failed [duplicate]

This question already has answers here:
How do I use sudo to redirect output to a location I don't have permission to write to? [closed]
(15 answers)
sudo cat << EOF > File doesn't work, sudo su does [duplicate]
(5 answers)
Closed 1 year ago.
I am trying to automate the addition of a repository source in my arch's pacman.conf file but using the echo command in my shell script. However, it fails like this:-
sudo echo "[archlinuxfr]" >> /etc/pacman.conf
sudo echo "Server = http://repo.archlinux.fr/\$arch" >> /etc/pacman.conf
sudo echo " " >> /etc/pacman.conf
-bash: /etc/pacman.conf: Permission denied
If I make changes to /etc/pacman.conf manually using vim, by doing
sudo vim /etc/pacman.conf
and quiting vim with :wq, everything works fine and my pacman.conf has been manually updated without "Permission denied" complaints.
Why is this so? And how do I get sudo echo to work? (btw, I tried using sudo cat too but that failed with Permission denied as well)
As #geekosaur explained, the shell does the redirection before running the command. When you type this:
sudo foo >/some/file
Your current shell process makes a copy of itself that first tries to open /some/file for writing, then if that succeeds it makes that file descriptor its standard output, and only if that succeeds does it execute sudo. This is failing at the first step.
If you're allowed (sudoer configs often preclude running shells), you can do something like this:
sudo bash -c 'foo >/some/file'
But I find a good solution in general is to use | sudo tee instead of > and | sudo tee -a instead of >>. That's especially useful if the redirection is the only reason I need sudo in the first place; after all, needlessly running processes as root is precisely what sudo was created to avoid. And running echo as root is just silly.
echo '[archlinuxfr]' | sudo tee -a /etc/pacman.conf >/dev/null
echo 'Server = http://repo.archlinux.fr/$arch' | sudo tee -a /etc/pacman.conf >/dev/null
echo ' ' | sudo tee -a /etc/pacman.conf >/dev/null
I added > /dev/null on the end because tee sends its output to both the named file and its own standard output, and I don't need to see it on my terminal. (The tee command acts like a "T" connector in a physical pipeline, which is where it gets its name.) And I switched to single quotes ('...') instead of doubles ("...") so that everything is literal and I didn't have to put a backslash in front of the $ in $arch. (Without the quotes or backslash, $arch would get replaced by the value of the shell parameter arch, which probably doesn't exist, in which case the $arch is replaced by nothing and just vanishes.)
So that takes care of writing to files as root using sudo. Now for a lengthy digression on ways to output newline-containing text in a shell script. :)
To BLUF it, as they say, my preferred solution would be to just feed a here-document into the above sudo tee command; then there is no need for cat or echo or printf or any other commands at all. The single quotation marks have moved to the sentinel introduction <<'EOF', but they have the same effect there: the body is treated as literal text, so $arch is left alone:
sudo tee -a /etc/pacman.conf >/dev/null <<'EOF'
[archlinuxfr]
Server = http://repo.archlinux.fr/$arch
EOF
But while that's how I'd do it, there are alternatives. Here are a few:
You can stick with one echo per line, but group all of them together in a subshell, so you only have to append to the file once:
(echo '[archlinuxfr]'
echo 'Server = http://repo.archlinux.fr/$arch'
echo ' ') | sudo tee -a /etc/pacman.conf >/dev/null
If you add -e to the echo (and you're using a shell that supports that non-POSIX extension), you can embed newlines directly into the string using \n:
# NON-POSIX - NOT RECOMMENDED
echo -e '[archlinuxfr]\nServer = http://repo.archlinux.fr/$arch\n ' |
sudo tee -a /etc/pacman.conf >/dev/null
But as it says above, that's not POSIX-specified behavior; your shell might just echo a literal -e followed by a string with a bunch of literal \ns instead. The POSIX way of doing that is to use printf instead of echo; it automatically treats its argument like echo -e does, but doesn't automatically append a newline at the end, so you have to stick an extra \n there, too:
printf '[archlinuxfr]\nServer = http://repo.archlinux.fr/$arch\n \n' |
sudo tee -a /etc/pacman.conf >/dev/null
With either of those solutions, what the command gets as an argument string contains the two-character sequence \n, and it's up to the command program itself (the code inside printf or echo) to translate that into a newline. In many modern shells, you have the option of using ANSI quotes $'...', which will translate sequences like \n into literal newlines before the command program ever sees the string. That means such strings work with any command whatsoever, including plain old -e-less echo:
echo $'[archlinuxfr]\nServer = http://repo.archlinux.fr/$arch\n ' |
sudo tee -a /etc/pacman.conf >/dev/null
But, while more portable than echo -e, ANSI quotes are still a non-POSIX extension.
And again, while those are all options, I prefer the straight tee <<EOF solution above.
The problem is that the redirection is being processed by your original shell, not by sudo. Shells are not capable of reading minds and do not know that that particular >> is meant for the sudo and not for it.
You need to:
quote the redirection ( so it is passed on to sudo)
and use sudo -s (so that sudo uses a shell to process the quoted redirection.)
http://www.innovationsts.com/blog/?p=2758
As the instructions are not that clear above I am using the instructions from that blog post. With examples so it is easier to see what you need to do.
$ sudo cat /root/example.txt | gzip > /root/example.gz
-bash: /root/example.gz: Permission denied
Notice that it’s the second command (the gzip command) in the pipeline that causes the error. That’s where our technique of using bash with the -c option comes in.
$ sudo bash -c 'cat /root/example.txt | gzip > /root/example.gz'
$ sudo ls /root/example.gz
/root/example.gz
We can see form the ls command’s output that the compressed file creation succeeded.
The second method is similar to the first in that we’re passing a command string to bash, but we’re doing it in a pipeline via sudo.
$ sudo rm /root/example.gz
$ echo "cat /root/example.txt | gzip > /root/example.gz" | sudo bash
$ sudo ls /root/example.gz
/root/example.gz
sudo bash -c 'echo "[archlinuxfr]" >> /etc/pacman.conf'
STEP 1 create a function in a bash file (write_pacman.sh)
#!/bin/bash
function write_pacman {
tee -a /etc/pacman.conf > /dev/null << 'EOF'
[archlinuxfr]
Server = http://repo.archlinux.fr/\$arch
EOF
}
'EOF' will not interpret $arch variable.
STE2 source bash file
$ source write_pacman.sh
STEP 3 execute function
$ write_pacman
append files (sudo cat):
cat <origin-file> | sudo tee -a <target-file>
append echo to file (sudo echo):
echo <origin> | sudo tee -a <target-file>
(EXTRA) disregard the ouput:
echo >origin> | sudo tee -a <target-file> >/dev/null

Unable to capture command exit code in makefile

I'm trying to setup my first makefile and am hitting a block at step 1. In my shell script, I did this:
which brew | grep 'brew not found' >/dev/null 2>&1
if [ $? == 0 ]; then
xcode-select --install
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
fi
This worked just fine as a bash script. After some googling, for a Makefile, I've so far come up with this one command:
BREW_INSTALLED = $(shell which brew | grep 'brew not found' >/dev/null 2>&1; echo $$?)
However, running it gets me
make: BREW_INSTALLED: No such file or directory
I'm equally unsure when I should be adding # to a command (seems like anything I don't want to output?).
I'm currently on GNU Make 3.81.
There are several odds in this line:
BREW_INSTALLED = $(shell which brew | grep 'brew not found' >/dev/null 2>&1; echo $$?)
In case of success, which writes its output to stdout, in case of failure to stderr. You are trying to capture the error message on stdout.
To feed the stderr of which to grep, you would need to write
which brew 2>&1 >/dev/null | grep 'brew not found'
(The order of 2>&1 and > also matters).
But you should not rely on the specific error message of which.
But you already get the return code you want from which, so you don't need grep at all.
Which returns the number of failed arguments, or -1 when no `programname' was given.
https://linux.die.net/man/1/which
Consider using grep -q 'expression' to supress output instead of redirecting stdout and stderr.
-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if an error was detected.
https://linux.die.net/man/1/grep
And the error message you get has nothing to do with what I'm writing above. This means the shell is trying to run BREW_INSTALLED as command, which probably means that make puts it at the beginning of a new shell.
Maybe you wrote it after a tabspace? see https://www.gnu.org/software/make/manual/html_node/Recipe-Syntax.html
To capture the return code (as string!):
BREW_INSTALLED := $(shell which brew >/dev/null 2>&1; echo $$?)
A typical makefile would check the presence of needed tools like this:
BREW := $(shell which brew)
# Check if variable brew is empty
ifeq ($(BREW),)
$(error brew not found)
else
$(info brew found: $(BREW))
endif
all:
#echo "Do something with brew"
$(BREW) --version
Note: There must be no tabspaces in the first two indented lines.
The two Recipe lines if the all Rule have to be indented with tabs.
The # at the beginning of a recipe supresses echoing: https://www.gnu.org/software/make/manual/html_node/Echoing.html

How to use dpkg in silent mode

I try to test if some packages are installed in my script before run it.
To do that by the dpkg command. This is my code :
dpkg -s dialog
dialogStatut=$?
if [ "$dialogStatut" -eq 1 ]; then
//Install package
fi
I would like to make dpkg in silent mode (without echo).
I have tried to put >&- 2>&- behind the command but if i do that the value is always 2 (if dialog is installed or not).
I have don't find solution in man dpkg.
What is the best way to do that?
You are looking for 2> /dev/null
if ! dpkg -s dialog 2> /dev/null; then
...
fi
Consider just exiting your script to let dialog be installed explicitly rather than making your script responsible for doing so.
I would do something like
dpkg -l dialog &>/dev/null || apt-get install dialog
The speciality with OR(||) if the first condition evaluates to true(ie an exit status of zero), then the second condition will not be evaluated.

Resources