Get the executed command, quoted params, after executing `"${argv[#]}"` - linux

This function works:
source foo.bash && foo -n "a b c.txt"
The problem is, no matter what I've tried, I couldn't get the last line echo "$CMD" (or echo $CMD) to generate exactly this output:
cat -n "a b c.txt"
How to achieve that?
# foo.bash
function foo() {
local argv=("$#");
local OUT=`cat "${argv[#]}"`
local CMD=`echo cat "${argv[#]}"`
echo "--------------------------"
echo "$OUT"
echo "--------------------------"
echo "$CMD"
}
The output is instead:
cat -n a b c.txt
With this command: foo -n \"a b c.txt\" it does work for the display of the command, but it gives errors for the execution via the backtick.
The file "a b c.txt" is a valid, small, text file.

You need to escape quotes inside of the assignment:
local CMD="cat \"${argv[#]}\""
Also, echo is not needed to concatenate strings.

There you go, with the help of number of tokens in bash variable I've come up with the right solution.
I've almost forgot WHY we actually need quoting for one argument, it's because it has multiple words!
function foo() {
local argv=( "$#" );
local OUT=`cat "${argv[#]}"`
echo "--------------------------"
echo "$OUT"
echo "--------------------------"
local CMD="cat"
for word in "${argv[#]}"; do
words="${word//[^\ ]} "
if [[ ${#words} > 1 ]]; then
local CMD="$CMD \"${word}\""
else
local CMD="$CMD $word"
fi
done
echo "$CMD"
}
Hope it helps someone.

Related

bash/ksh grep script take more than one argument

#!/bin/ksh
if [ -n "$1" ]
then
if grep -w -- "$1" codelist.lst
then
true
else
echo "Value not Found"
fi
else
echo "Please enter a valid input"
fi
This is my script and it works exactly how I want at the moment, I want to add if I add more arguments It will give me the multiple outputs, How can I do that?
So For Example I do ./test.sh apple it will grep apple in codelist.lst and Give me the output : Apple
I want to do ./test.sh apple orange and will do:
Apple
Orange
You can do that with shift and a loop, something like (works in both bash and ksh):
for ((i = $#; i > 0 ; i--)) ; do
echo "Processing '$1'"
shift
done
You'll notice I've also opted not to use the [[ -n "$1" ]] method as that would terminate the loop early with an empty string (such as with ./script.sh a b "" c stopping without doing c).
To iterate over the positional parameters:
for pattern in "$#"; do
grep -w -- "$pattern" codelist.lst || echo "'$pattern' not Found"
done
For a more advanced usage, which only invokes grep once, use the -f option with a shell process substitution:
grep -w -f <(printf '%s\n' "$#") codelist.lst

How to make "dictionary" with shell functions?

This is my code:
#!/bin/sh
echo "ARGUMENTS COUNT : " $#
echo "ARGUMENTS LIST : " $*
dictionary=`awk '{ print $1 }'`
function()
{
for i in dictionary
do
for j in $*
do
if [ $j = $i ]
then
;
else
append
fi
done
done
}
append()
{
ls $j > dictionary1.txt
}
function
I need using unix shell functions make "dictionary". For example: I write in arguments default word, example hello. Then my function checks the file dictionary1 if that word is existing in the file. If not - append that word in file, if it's already exist - do nothing.
For some reason, my script does not work. When I start my script, it waits for something and that's it.
What I am doing wrong? How can I fix it?
An implementation that tries to care about both performance and correctness might look like:
#!/usr/bin/env bash
# ^^^^- NOT sh; sh does not support [[ ]] or <(...)
addWords() {
local tempFile dictFile
tempFile=$(mktemp dictFile.XXXXXX) || return
dictFile=$1; shift
[[ -e "$dictFile" ]] || touch "$dictFile" || return
sort -um "$dictFile" <(printf '%s\n' "$#" | sort -u) >"$tempFile"
mv -- "$tempFile" "$dictFile"
}
addWords myDict beta charlie delta alpha
addWords myDict charlie zulu
cat myDict
...has a final dictionary state of:
alpha
beta
charlie
delta
zulu
...and it rereads the input file only once for each addWords call (no matter how many words are being added!), not once per word to add.
Don't name a function "function".
Don't read in and walk through the whole file - all you need is to know it the word is there or not. grep does that.
ls lists files. You want to send a word to the file, not a filename. use echo or printf.
sh isn't bash. Use bash unless there's a clear reason not to, and the only reason is because it isn't available.
Try this:
#! /bin/env bash
checkWord() {
grep -qm 1 "$1" dictionary1.txt ||
echo "$1" >> dictionary1.txt
}
for wd
do checkWord "$wd"
done
If that works, you can add more structure and error checking.
You can remove your dictionary=awk... line (as mentioned it's blocking waiting for input) and simply grep your dictionary file for each argument, something like the below :
for i in "$#"
do
if ! grep -qow "$i" dictionary1.txt
then
echo "$i" >> dictionary1.txt
fi
done
With any awk in any shell on any UNIX box:
awk -v words="$*" '
BEGIN {
while ( (getline word < "dictionary1.txt") > 0 ) {
dict[word]++
}
close("dictionary1.txt")
split(words,tmp)
for (i in tmp) {
word = tmp[i]
if ( !dict[word]++ ) {
newWords = newWords word ORS
}
}
printf "%s", newWords >> "dictionary1.txt"
exit
}'

bash separate parameters with specific delimiter

I am searching for a command, that separates all given parameters with a specific delimiter, and outputs them quoted.
Example (delimiter is set to be a colon :):
somecommand "this is" "a" test
should output
"this is":"a":"test"
I'm aware that the shell interprets the "" quotes before passing the parameters to the command. So what the command should actually do is to print out every given parameter in quotes and separate all these with a colon.
I'm also not seeking for a bash-only solution, but for the most elegant solution.
It is very easy to just loop over an array of these elements and do that, but the problem is that I have to use this inside a gnu makefile which only allows single line shell commands and uses sh instead of bash.
So the simpler the better.
How about
somecommand () {
printf '"%s"\n' "$#" | paste -s -d :
}
Use printf to add the quotes and print every entry on a separate line, then use paste with the -s ("serial") option and a colon as the delimiter.
Can be called like this:
$ somecommand "this is" "a" test
"this is":"a":"test"
apply_delimiter () {
(( $# )) || return
local res
printf -v res '"%s":' "$#"
printf '%s\n' "${res%:}"
}
Usage example:
$ apply_delimiter hello world "how are you"
"hello":"world":"how are you"
As indicated in a number of the comments, a simple "loop-over" approach, looping over each of the strings passed as arguments is a fairly straight-forward way to approach it:
delimit_colon() {
local first=1
for i in "$#"; do
if [ "$first" -eq 1 ]; then
printf "%s" "$i"
first=0
else
printf ":%s" "$i"
fi
done
printf "\n"
}
Which when combined with a short test script could be:
#!/bin/bash
delimit_colon() {
local first=1
for i in "$#"; do
if [ "$first" -eq 1 ]; then
printf "%s" "$i"
first=0
else
printf ":%s" "$i"
fi
done
printf "\n"
}
[ -z "$1" ] && { ## validate input
printf "error: insufficient input\n"
exit 1
}
delimit_colon "$#"
exit 0
Test Input/Output
$ bash delimitargs.sh "this is" "a" test
this is:a:test
Here a solution using the z-shell:
#!/usr/bin/zsh
# this is "somecommand"
echo '"'${(j_":"_)#}'"'
If you have them in an array already, you can use this command
MYARRAY=("this is" "a" "test")
joined_string=$(IFS=:; echo "$(MYARRAY[*])")
echo $joined_string
Setting the IFS (internal field separator) will be the character separator. Using echo on the array will display the array using the newly set IFS. Putting those commands in $() will put the output of the echo into joined_string.

How to enable or disable multiple "echo statements" in bash ecript

I have bash script where i have echo before every command showing what is happening.
But i need to disbale echo when setting as cron job and then enable again if do some testing.
i find it very hard to go to each line and then add/remove comment
is there anything which i can include at top something like
enable echo or disable echo
so that i don't have to waste time
The absolute easiest would be to insert the following line after the hashbang line:
echo() { :; }
When you want to re-enable, either delete the line or comment it out:
#echo() { :; }
If you're not using echo but printf, same strategy, i.e.:
printf() { :; }
If you absolutely need to actually echo/printf something, prepend the builtin statement, e.g.:
builtin echo "This 'echo' will not be suppressed."
This means that you can do a conditional output, e.g.:
echo () {
[[ "$SOME_KIND_OF_FLAG" ]] && builtin echo $#
}
Set the SOME_KIND_OF_FLAG variable to something non-null, and the overridden echo function will behave like normal echo.
EDIT: another alternative would be to use echo for instrumenting (debugging), and printf for the outputs (e.g., for piping purposes). That way, no need for any FLAG. Just disable/enable the echo() { :; } line according to whether you want to instrument or not, respectively.
Enable/Disable via CLI Parameter
Put these lines right after the hashbang line:
if [[ debug == "$1" ]]; then
INSTRUMENTING=yes # any non-null will do
shift
fi
echo () {
[[ "$INSTRUMENTING" ]] && builtin echo $#
}
Now, invoking the script like this: script.sh debug will turn on instrumenting. And because there's the shift command, you can still feed parameters. E.g.:
Without instrumenting: script.sh param1 param2
With instrumenting: script.sh debug param1 param2
The above can be simplified to:
if [[ debug != "$1" ]]; then
echo () { :; }
shift
fi
if you need the instrumenting flag (e.g. to record the output of a command to a temp file only if debugging), use an else-block:
if [[ debug != "$1" ]]; then
echo () { :; }
shift
else
INSTRUMENTING=yes
fi
REMEMBER: in non-debug mode, all echo commands are disabled; you have to either use builtin echo or printf. I recommend the latter.
Several things:
Don't use echo at all
Instead use set -xv to set debug mode which will echo each and every command. You can set PS4 to the desired prompt: for example PS4='$LINENO: ' will print out the line number on each line. In BASH, I believe it's the same. Then, you don't have to clean up your script. To shut off, use set +xv.
Example:
foo=7
bar=7
PS4='$LINENO: '
set -xv #Begin debugging
if [ $foo = $bar ]
then
echo "foo certainly does equal bar"
fi
set +xv #Debugging is off
if [ $bar = $foo ]
then
echo "And bar also equals foo"
fi
Results:
$ myprog.sh
if [ $foo = $bar ]
then
echo "foo certainly does equal bar"
fi
5: [ 7 = 7 ]
7: echo 'foo certainly does equal bar'
foo certainly does equal bar
set +xv #Debugging is off
And bar also equals foo
Use a function
Define a function instead of using echo:
Example:
function myecho {
if [ ! -z "$DEBUG" ]
then
echo "$*"
fi
}
DEBUG="TRUE"
my echo "Will print out this line"
unset DEBUG
myecho "But won't print out this line"
Use the nop command
The colon (:) is the nop command in BASH. It doesn't do anything. Use an environment variable and define it as either echo or :. When set to a colon, nothing happens. When set to echo, the line prints.
Example:
echo=":"
$echo "This line won't print"
echo="echo"
$echo "But this line will."
Building on Matthew's answer, how about something like this:
myEcho = "/bin/true"
if [ ! "$CRON" ]: then
myEcho = "/bin/echo"
fi
and then use $myEcho instead of echo in your script?
You can do one better. If you setup your crontab as detailed in another answer, you can then check if you are running in cron and only print if you are not. This way you don't need to modify your script at all between different runs.
You should then be able to use something like this (probably doesn't quite work, I'm not proficient in bash):
if [ ! "$CRON" ]; then
echo "Blah blah"
fi
Try set -v at the top to echo each command. To stop echoing change it to set +v.
Not sure if I miss the below solution to use a variable (e.g. debug) at the start of the bash script.
Once you set the debug=true, any conditional-if will enable or disable multiple “echo statements” in bash script.
typeset debug=false # set to true if need to debug
...
if [ $debug == "true" ]; then
echo
echo "Filter"
read
fi
...
if [ $debug == "true" ]; then
echo
echo "to run awk"
fi
Couldn't post a code block in a comment, so I'll post this as an answer.
If you're a perfectionist (like I am) and don't want the last set +x line to be printed... and instead print Success or FAIL, this works:
(
set -e # Stop at first error
set -x # Print commands
set -v # Print shell input lines as they are read
git pull
// ...other commands...
) && echo Success || echo FAIL
It will create a sub process, though, which may be an overkill solution.
If you're running it in cron, why not just dump the output? Change your crontab entry so that it has > /dev/null at the end of the command, and all output will be ignored.

shell script to compare files and print formatted output

I'm trying to write a shell script which will compare two files, and if there are no differences between then, it will indicate that there was a success, and if there are differences, it will indicate that there was a failure, and print the results. Here's what I have so far:
result = $(diff -u file1 file2)
if [ $result = "" ]; then
echo It works!
else
echo It does not work
echo $result
fi
Anybody know what I'm doing wrong???
result=$(diff -u file1 file2)
if [ $? -eq 0 ]; then
echo "It works!"
else
echo "It does not work"
echo "$result"
fi
Suggestions:
No spaces around "=" in the variable assignment for results
Use $? status variable after running diff instead of the string length of $result.
I'm in the habit of using backticks for command substitution instead of $(), but #Dennis Williamson cites some good reasons to use the latter after all. Thanks Dennis!
Applied quotes per suggestions in comments.
Changed "=" to "-eq" for numeric test.
First, you should wrap strings being compared with quotes.
Second, "!" cannot be use it has another meaning. You can wrap it with single quotes.
So your program will be.
result=$(diff -u file1 file2)
if [ "$result" == "" ]; then
echo 'It works!'
else
echo It does not work
echo "$result"
fi
Enjoy.
Since you need results when you fail, why not simply use 'diff -u file1 file2' in your script? You may not even need a script then. If diff succeeds, nothing will happen, else the diff will be printed.
bash string equivalence is "==".
-n is non-zero string, -z is zero length string, wrapping in quotes because the command will complain if the output of diff is longer than a single string with "too many arguments".
so
if [ -n "$(diff $1 $2)" ]; then
echo "Different"
fi
or
if [ -z "$(diff $1 $2)" ]; then
echo "Same"
fi

Resources