I have written the following Bash script. Its role is to check its own name, and in case of nonexistent extension , to amend ".sh" with sed. Still I have error "missing target file..."
#!/bin/bash
FILE_NAME="$0"
EXTENSION=".sh"
FILE_NAME_MOD="$FILE_NAME$EXTENSION"
if [[ "$0" != "FILE_NAME_MOD" ]]; then
echo mv -v "$FILENAME" "$FILENAME$EXTENSION"
cp "$0" | sed 's/\([^.sh]\)$/\1.sh/g' $0
fi
#!/bin/bash
file="$0"
extension=".sh"
if [ $(echo -n $file | tail -c 3) != $extension ]; then
mv -v "$file" "$file$extension"
fi
Important stuff:
-n flag suppress the new line at the end, so we can test for 3 chars instead of 4
When in doubt, always use set -x to debug your scripts.
Try this Shellcheck-clean code:
#! /bin/bash -p
file=${BASH_SOURCE[0]}
extension=.sh
[[ $file == *"$extension" ]] || mv -i -- "$file" "$file$extension"
See choosing between $0 and BASH_SOURCE for details of why ${BASH_SOURCE[0]} is better than $0.
See Correct Bash and shell script variable capitalization for details of why file is better than FILE and extension is better than EXTENSION. (In short, ALL_UPPERCASE names are dangerous because there is a danger that they will clash with names that are already used for something else.)
The -i option to mv means that you will be prompted to continue if the new filename is already in use.
See Should I save my scripts with the .sh extension? before adding .sh extensions to your shell programs.
Just for fun, here is a way to do it just with GNU sed:
#!/usr/bin/env bash
sed --silent '
# match FILENAME only if it does not end with ".sh"
/\.sh$/! {
# change "FILENAME" to "mv -v FILENAME FILENAME.sh"
s/.*/mv -v & &.sh/
# execute the command
e
}
' <<<"$0"
You can also make the above script output useful messages:
#!/usr/bin/env bash
sed --silent '
/\.sh$/! {
s/.*/mv -v & &.sh/
e
# exit with code 0 immediately after the change has been made
q0
}
# otherwise exit with code 1
q1
' <<<"$0" && echo 'done' || echo 'no changes were made'
I want to run following bash script from C++ code. I tries to use system() or popen to run commands and capture its output but they but I get errors because built-in sh tries to execute it, such as,
sh: 6: [[: not found
sh: 8: [[: not found
sh: 9: [[: not found
I tried bash -c as well but that also produced errors because I think it doesn't handle multiline string.
I can't put below script in to .sh file and run it because of several reasons. So this script needs to be stored as a string in C++ code and get executed. Any idea how this can be done?
#!/bin/bash
for sysdevpath in $(find /sys/bus/usb/devices/usb*/ -name dev); do
(
syspath="${sysdevpath%/dev}"
devname="$(udevadm info -q name -p $syspath)"
[[ "$devname" == "bus/"* ]] && continue
eval "$(udevadm info -q property --export -p $syspath)"
[[ -z "$ID_SERIAL" ]] && continue
[[ "${ID_SERIAL}" == *"PX4"* ]] && echo "/dev/$devname"
)
done
Sample code:
Note: You can use this tool to convert text to C++ escapped string.
int main() {
std::cout << system("#!/bin/bash\nfor sysdevpath in $(find /sys/bus/usb/devices/usb*/ -name dev); do\n (\n syspath=\"${sysdevpath%/dev}\"\n devname=\"$(udevadm info -q name -p $syspath)\"\n [[ \"$devname\" == \"bus/\"* ]] && continue\n eval \"$(udevadm info -q property --export -p $syspath)\"\n [[ -z \"$ID_SERIAL\" ]] && continue\n [[ \"${ID_SERIAL}\" == *\"PX4\"* ]] && echo \"/dev/$devname\"\n )\ndone");
return 0;
}
You can turn a multiline bash script to single-line. Let's assume you have the following bash script:
FOO=`uname`
if [ "$FOO" == "Linux" ]; then
echo "You are using 'Linux'"
fi
The code above can be transformed into single-line by using semicolons:
FOO=`uname`; if [ "$FOO" == "Linux" ]; then echo "You are using 'Linux'"; fi
Now with proper escaping you can use system command to execute it from your c++ program as follows:
#include <cstdlib>
#include <string>
int main() {
std::string foo {
"bash -c '"
"FOO=`uname`; "
"if [ \"$FOO\" == \"Linux\" ]; then "
"echo \"You are using 'Linux'.\"; "
"fi'"
};
system(foo.c_str());
}
Note that adjacent string literals are concatenated by the compiler, so you can still make it look like a multiline script for better readability.
I have a log file. I want to scan the occurrences of the string
"RC = x"
If there is >=1 one occurrence where RC = 0 is not TRUE then I want to flag it as failure
Can I do this as shell function?
You can use grep for this:
grep -q "RC = [^0]" logfile && {
echo "Logfile is a failure"
other commands
exit
}
The -q puts grep into silence mode: no output, the [^0] matches any non-zero value for RC. Grep will return a failure status if no matches are found and a success status if any are. The && is a conditional "and" so if there are any non-zero values for RC, then the grep will succeed and the test is a failure.
Alternately, you can put the grep into an if statement. In this case:
if grep -s "RC = [^0]" logfile
then
echo "Logfile is a failure"
else
echo "Logfile is a success"
fi
See man grep for more grep options.
I need to use cron and bash to check if IP 111.222.333.444 is still valid for host sub.domain.com.
I have tried grep with -Pzo, but it didn't work. I don't want to install pcregrep.
#!/bin/bash
ipaddressused=$1
#Run a dig for sub.domain.com:
ipaddresscurrent='dig +short sub.domain.com'
echo "$ipaddresscurrent" | grep -Pzo "$ipaddressused" && echo "found" && exit 0 || echo "not found" && exit 1
ipaddresscurrent returns multiple IPs, one per line.
How do I make this work?
Wouldn't this be enough?
#!/bin/bash
ipaddressused=$1
if grep -q -P "$ipaddressused" < <(dig +short sub.domain.com); then
echo "found"
exit 0
else
echo "not found"
exit 1
fi
What was wrong with your script?
The line
ipaddresscurrent='dig +short sub.domain.com'
assigns the string dig +short sub.domain.com to the variable ipaddresscurrent. You probably, instead, wanted to assign to the variable ipaddresscurrent the output of the command dig +short sub.domain.com. This is done either using the old and deprecated backticks:
ipaddresscurrent=`dig +short sub.domain.com`
(but please don't ever use backticks!) or the more modern, robust and nestable $(...) as:
ipaddresscurrent=$(dig +short sub.domain.com)
grep -Pzo doesn't really do what you're expecting. Instead you want to run grep quiently (hence the -q flag) and check its output, so the following would have been valid:
echo "$ipaddresscurrent" | grep -q -P "$ipaddressused" && echo "found" && exit 0 || echo "not found" && exit 1
Since you don't really need the variable ipaddresscurrent, I prefered to use bash's process substitution to feed grep.
Also, don't use long chains of && || &&'s, it's hard to read, and can have some subtle side effects.
If you want to stick with your variable, you need a here-string as so:
#!/bin/bash
ipaddressused=$1
ipaddresscurrent=$(dig +short sub.domain.com)
if grep -q -P "$ipaddressused" <<< "$ipaddresscurrent"; then
echo "found"
exit 0
else
echo "not found"
exit 1
fi
As you note in your comment:
should be noted that if the $ipaddressused supplied is 111.222.333.4 and 111.222.333.456 is present on the list a match will also occur. this could cause problems.
I actually didn't really know if this was a requested feature or not (since the argument of the script is a regex, that's actually why I left the -P flag). If you really want to exactly match an IP, here's how you could proceed:
#!/bin/bash
if grep -q "^${1//./\.}$" < <(dig +short sub.domain.com); then
echo "found"
exit 0
else
echo "not found"
exit 1
fi
assuming that dig used this way will output only one ip per line.
How can I reduce the following bash script?
grep -P "STATUS: (?!Perfect)" recess.txt && exit 1
exit 0
It seems like I should be able to do it with a single command, but I have a total of 3 here.
My program should:
Read recess.txt
Exit 1 (or non-zero) if it contains a line with "STATUS: " of NOT "Perfect"
Exit 0 if no such line exists (i.e. all "STATUS: " lines are "Perfect")
The answer award goes to the tightest script. Thanks!
Example files
Program should have exit status 0 for this file:
FILE: styles.css
STATUS: Perfect!
FILE: contour-styles.css
STATUS: Perfect!
Program should have exit status 1 (or non-zero) for this file:
FILE: styles.css
STATUS: Perfect!
FILE: contour-styles.css
STATUS: Busted
FAILURES: 1 failure
Id's should not be styled
1. #asdf
Just negate the return value.
! grep -P "STATUS: (?!Perfect)" recess.txt
I came across this, needing an onlyif statement for Puppet. As such, Tgr's bash solution wouldn't work, and I didn't want to expand the complexity as in Christopher Neylan's answer.
I ended up using a version inspired by Henri Schomäcker's answer, but notably simplified:
grep -P "STATUS: (?!Perfect)" recess.txt; test $? -eq 1
Which very simply inverts the exit code, returning success only if the text is not found:
If grep returns 0 (match found), test 0 -eq 1 will return 1.
If grep returns 1 (no match found), test 1 -eq 1 will return 0.
If grep returns 2 (error), test 2 -eq 1 will return 1.
Which is exactly what I wanted: return 0 if no match is found, and 1 otherwise.
if anyone gets here looking for a bash return code manipulation:
(grep <search> <files> || exit 0 && exit 123;)
this will return 0 (success) when grep finds nothing, and return 123 (failure) when it does. The parenthesis are in case anyone test it as is on the shell prompt. with parenthesis it will not logout on the exit, but just exit the subshell with the same error code.
i use it for a quick syntax check on js files:
find src/js/ -name \*js -exec node \{\} \; 2>&1 | grep -B 5 SyntaxError || exit 0 && exit 1;
To make it work with set -e surround it in a sub-shell with ( and ):
$ cat test.sh
#!/bin/bash
set -ex
(! ls /tmp/dne)
echo Success
$ ./test.sh
+ ls /tmp/dne
ls: cannot access /tmp/dne: No such file or directory
+ echo Success
Success
$ mkdir /tmp/dne
$ ./test.sh
+ ls /tmp/dne
$
Just negating the return value doesn't work in a set -e context. But you can do:
! grep -P "STATUS: (?!Perfect)" recess.txt || false
You actually don't need to use exit at all. Logically, no matter what the result of grep, your script is going to exit anyway. Since the exit value of a shell script is the exit code of the last command that was run, just have grep run as the last command, using the -v option to invert the match to correct the exit value. Thus, your script can reduce to just:
grep -vqP "STATUS: (?!Perfect)" recess.txt
EDIT:
Sorry, the above does not work when there are other types of lines in the file. In the interest of avoiding running multiple commands though, awk can accomplish the entire shebang with something like:
awk '/STATUS: / && ! /Perfect/{exit 1}' recess.txt
If you decide you want the output that grep would have provided, you can do:
awk '/^STATUS: / && ! /Perfect/{print;ec=1} END{exit ec}' recess.txt
Use the special ? variable:
grep -P "STATUS: (?!Perfect)" recess.txt
exit $((1-$?))
(But note that grep may also return 2, so it's not clear what you'd want to occur in such cases.)
The problem with the grep answers is that if the file is empty you also get a clean response, as if the file had a perfect.
So personally I gave up on grep for this and used awk.
awk 'BEGIN{ef=2}; /STATUS: Perfect/{ ef=0;}; /STATUS: Busted/{ print;eff=3;}; END{exit (ef+eff)}' a.txt ; echo $?
This has exit status:
0 : Perfect and !Busted
2 : !Perfect and Busted
3 : Perfect and Busted
5 : !Perfect and !Busted
[ $(grep -c -P "STATUS: (?!Perfect)" recess.txt) -eq 0 ]
I also needed such a solution for writing puppet only if statements and came up with the following command:
/bin/grep --quiet 'root: root#ourmasterdomain.de' /etc/aliases; if [ $? -eq 0 ]; then test 1 -eq 2; else test 1 -eq 1; fi;
Since someone already posted a Puppet solution, I might as well add how to invert a shell command run by Ansible:
- name: Check logs for errors
command: grep ERROR /var/log/cassandra/system.log
register: log_errors
failed_when: "log_errors.rc == 0"
I.e. you just set the failed condition to the return code being 0. So this command fails if we do find the word ERROR in our logs.
I chose this rather than grep -v as that also inverts grep's output, so we would receive all DEBUG/INFO/WARN lines in log_errors.stdout_lines which we do not want.