/bin/dash: Bad substitution - string

I need to do a string manipuilation in shell script (/bin/dash):
#!/bin/sh
PORT="-p7777"
echo $PORT
echo ${PORT/p/P}
the last echo fails with Bad substitution. When I change shell to bash, it works:
#!/bin/bash
PORT="-p7777"
echo $PORT
echo ${PORT/p/P}
How can I implement the string substitution in dash ?

The substitution you're using is not a basic POSIX feature (see here, in section 2.6.2 Parameter Expansion), and dash doesn't implement it.
But you can do it with any of a number of external helpers; here's an example using sed:
PORT="-p7777"
CAPITOLPORT=$(printf '%s\n' "$PORT" | sed 's/p/P/')
printf '%s\n' "$CAPITOLPORT"
BTW, note that I'm using printf '%s\n' instead of echo -- that's because some implementations of echo do unpredictable things when their first argument starts with "-". printf is a little more complicated to use (you need a format string, in this case %s\n) but much more reliable. I'm also double-quoting all variable references ("$PORT" instead of just $PORT), to prevent unexpected parsing.
I'd also recommend switching to lower- or mixed-case variables. There are a large number of all-caps variable that have special meanings, and if you accidentally use one of those it can cause problems.

Using parameter expansion:
$ cat foo.sh
#!/bin/sh
PORT="-p7777"
echo $PORT
echo ${PORT:+-P${PORT#-p}}
PORT=""
echo $PORT
echo ${PORT:+-P${PORT#-p}}
Run it:
$ /bin/sh foo.sh
-p7777
-P7777
Update:
$ man dash:
- -
${parameter#word} Remove Smallest Prefix Pattern.
$ echo ${PORT#-p}
7777
$ man dash
- -
${parameter:+word} Use Alternative Value.
$ echo ${PORT:+-P${PORT#-p}}
-P7777

Related

Bash: New line in echo string fails when output is piped to crontab [duplicate]

How do I print a newline? This merely prints \n:
$ echo -e "Hello,\nWorld!"
Hello,\nWorld!
Use printf instead:
printf "hello\nworld\n"
printf behaves more consistently across different environments than echo.
Make sure you are in Bash.
$ echo $0
bash
All these four ways work for me:
echo -e "Hello\nworld"
echo -e 'Hello\nworld'
echo Hello$'\n'world
echo Hello ; echo world
echo $'hello\nworld'
prints
hello
world
$'' strings use ANSI C Quoting:
Words of the form $'string' are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard.
You could always do echo "".
For example,
echo "Hello,"
echo ""
echo "World!"
On the off chance that someone finds themselves beating their head against the wall trying to figure out why a coworker's script won't print newlines, look out for this:
#!/bin/bash
function GET_RECORDS()
{
echo -e "starting\n the process";
}
echo $(GET_RECORDS);
As in the above, the actual running of the method may itself be wrapped in an echo which supersedes any echos that may be in the method itself. Obviously, I watered this down for brevity. It was not so easy to spot!
You can then inform your comrades that a better way to execute functions would be like so:
#!/bin/bash
function GET_RECORDS()
{
echo -e "starting\n the process";
}
GET_RECORDS;
Simply type
echo
to get a new line
POSIX 7 on echo
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html
-e is not defined and backslashes are implementation defined:
If the first operand is -n, or if any of the operands contain a <backslash> character, the results are implementation-defined.
unless you have an optional XSI extension.
So I recommend that you should use printf instead, which is well specified:
format operand shall be used as the format string described in XBD File Format Notation [...]
the File Format Notation:
\n <newline> Move the printing position to the start of the next line.
Also keep in mind that Ubuntu 15.10 and most distros implement echo both as:
a Bash built-in: help echo
a standalone executable: which echo
which can lead to some confusion.
str='hello\nworld'
$ echo | sed "i$str"
hello
world
You can also do:
echo "hello
world"
This works both inside a script and from the command line.
On the command line, press Shift+Enter to do the line break inside the string.
This works for me on my macOS and my Ubuntu 18.04 (Bionic Beaver) system.
For only the question asked (not special characters etc) changing only double quotes to single quotes.
echo -e 'Hello,\nWorld!'
Results in:
Hello,
World!
There is a new parameter expansion added in Bash 4.4 that interprets escape sequences:
${parameter#operator} - E operator
The expansion is a string that is the value of parameter with
backslash escape sequences expanded as with the $'…' quoting
mechanism.
$ foo='hello\nworld'
$ echo "${foo#E}"
hello
world
I just use echo without any arguments:
echo "Hello"
echo
echo "World"
To print a new line with echo, use:
echo
or
echo -e '\n'
This could better be done as
x="\n"
echo -ne $x
-e option will interpret backslahes for the escape sequence
-n option will remove the trailing newline in the output
PS: the command echo has an effect of always including a trailing newline in the output so -n is required to turn that thing off (and make it less confusing)
My script:
echo "WARNINGS: $warningsFound WARNINGS FOUND:\n$warningStrings
Output:
WARNING : 2 WARNINGS FOUND:\nWarning, found the following local orphaned signature file:
On my Bash script I was getting mad as you until I've just tried:
echo "WARNING : $warningsFound WARNINGS FOUND:
$warningStrings"
Just hit Enter where you want to insert that jump. The output now is:
WARNING : 2 WARNINGS FOUND:
Warning, found the following local orphaned signature file:
If you're writing scripts and will be echoing newlines as part of other messages several times, a nice cross-platform solution is to put a literal newline in a variable like so:
newline='
'
echo "first line${newline}second line"
echo "Error: example error message n${newline}${usage}" >&2 #requires usage to be defined
If the previous answers don't work, and there is a need to get a return value from their function:
function foo()
{
local v="Dimi";
local s="";
.....
s+="Some message here $v $1\n"
.....
echo $s
}
r=$(foo "my message");
echo -e $r;
Only this trick worked on a Linux system I was working on with this Bash version:
GNU bash, version 2.2.25(1)-release (x86_64-redhat-linux-gnu)
You could also use echo with braces,
$ (echo hello; echo world)
hello
world
This got me there....
outstuff=RESOURCE_GROUP=[$RESOURCE_GROUP]\\nAKS_CLUSTER_NAME=[$AKS_CLUSTER_NAME]\\nREGION_NAME=[$REGION_NAME]\\nVERSION=[$VERSION]\\nSUBNET-ID=[$SUBNET_ID]
printf $outstuff
Yields:
RESOURCE_GROUP=[akswork-rg]
AKS_CLUSTER_NAME=[aksworkshop-804]
REGION_NAME=[eastus]
VERSION=[1.16.7]
SUBNET-ID=[/subscriptions/{subidhere}/resourceGroups/makeakswork-rg/providers/Microsoft.Network/virtualNetworks/aks-vnet/subnets/aks-subnet]
Sometimes you can pass multiple strings separated by a space and it will be interpreted as \n.
For example when using a shell script for multi-line notifcations:
#!/bin/bash
notify-send 'notification success' 'another line' 'time now '`date +"%s"`
With jq:
$ jq -nr '"Hello,\nWorld"'
Hello,
World
Additional solution:
In cases, you have to echo a multiline of the long contents (such as code/ configurations)
For example:
A Bash script to generate codes/ configurations
echo -e,
printf might have some limitation
You can use some special char as a placeholder as a line break (such as ~) and replace it after the file was created using tr:
echo ${content} | tr '~' '\n' > $targetFile
It needs to invoke another program (tr) which should be fine, IMO.

Writing variables to file with bash

I'm trying to configure a file with a bash script. And the variables in the bash script are not written in file as it is written in script.
Ex:
#!/bin/bash
printf "%s" "template("$DATE\t$HOST\t$PRIORITY\t$MSG\n")" >> /file.txt
exit 0
This results to template('tttn') instead of template("$DATE\t$HOST\t$PRIORITY\t$MSG\n in file.
How do I write in the script so that the result is template("$DATE\t$HOST\t$PRIORITY\t$MSG\n in the configured file?
Is it possible to write variable as it looks in script to file?
Enclose the strings you want to write within single quotes to avoid variable replacement.
> FOO=bar
> echo "$FOO"
bar
> echo '$FOO'
$FOO
>
Using printf in any shell script is uncommon, just use echo with the -e option.
It allows you to use ANSI C metacharacters, like \t or \n. The \n at the end however isn't necessary, as echo will add one itself.
echo -e "template(${DATE}\t${HOST}\t${PRIORITY}\t${MSG})" >> file.txt
The problem with what you've written is, that ANSI C metacharacters, like \t can only be used in the first parameter to printf.
So it would have to be something like:
printf 'template(%s\t%s\t%s\t%s)\n' ${DATE} ${HOST} ${PRIORITY} ${MSG} >> file.txt
But I hope we both agree, that this is very hard on the eyes.
There are several escaping issues and the power of printf has not been used, try
printf 'template(%s\t%s\t%s\t%s)\n' "${DATE}" "${HOST}" "${PRIORITY}" "${MSG}" >> file.txt
Reasons for this separate answer:
The accepted answer does not fit the title of the question (see comment).
The post with the right answer
contains wrong claims about echo vs printf as of this post and
is not robust against whitespace in the values.
The edit queue is full at the moment.

Remove substring matching pattern both in the beginning and the end of the variable

As the title says, I'm looking for a way to remove a defined pattern both at the beginning of a variable and at the end. I know I have to use # and % but I don't know the correct syntax.
In this case, I want to remove http:// at the beginning, and /score/ at the end of the variable $line which is read from file.txt.
Well, you can't nest ${var%}/${var#} operations, so you'll have to use temporary variable.
Like here:
var="http://whatever/score/"
temp_var="${var#http://}"
echo "${temp_var%/score/}"
Alternatively, you can use regular expressions with (for example) sed:
some_variable="$( echo "$var" | sed -e 's#^http://##; s#/score/$##' )"
$ var='https://www.google.com/keep/score'
$ var=${var#*//} #removes stuff upto // from begining
$ var=${var%/*} #removes stuff from / all the way to end
$ echo $var
www.google.com/keep
You have to do it in 2 steps :
$ string="fooSTUFFfoo"
$ string="${string%foo}"
$ string="${string#foo}"
$ echo "$string"
STUFF
There IS a way to do it one step using only built-in bash functionality (no running external programs such as sed) -- with BASH_REMATCH:
url=http://whatever/score/
re='https?://(.*)/score/'
[[ $url =~ $re ]] && printf '%s\n' "${BASH_REMATCH[1]}"
This matches against the regular expression on the right-hand side of the =~ test, and puts the groups into the BASH_REMATCH array.
That said, it's more conventional to use two PE expressions and a temporary variable:
shopt -s extglob
url=http://whatever/score/
val=${url#http?(s)://}; val=${val%/score/}
printf '%s\n' "$val"
...in the above example, the extglob option is used to allow the shell to recognized "extglobs" -- bash's extensions to glob syntax (making glob-style patterns similar in power to regular expressions), among which ?(foo) means that foo is optional.
By the way, I'm using printf rather than echo in these examples because many of echo's behaviors are implementation-defined -- for instance, consider the case where the variable's contents are -e or -n.
how about
export x='https://www.google.com/keep/score';
var=$(perl -e 'if ( $ENV{x} =~ /(https:\/\/)(.+)(\/score)/ ) { print qq($2);}')

Auto-escaping an IP address in bash alias $argv

I want to set up a bash alias to grep all logs in a directory automatically; however, to make this user-friendly, I need to escape the periods and add a whitespace boundary so grep won't match too many lines.
First I checked to be sure that I had the right syntax to escape an address...
[mpenning#sasmars daily]$ echo 1.1.1.1 | sed "s/\./\\\\./g"
1\.1\.1\.1
[mpenning#sasmars daily]$
Next I tried to escape a CLI argument... but it's not quite getting me there...
[mpenning#sasmars daily]$ alias tryme='echo `sed "s/$argv[1]/\\\\./g"`'
[mpenning#sasmars daily]$ tryme 1.1.1.1
-> Indefinite hang until I hit cntlc
I realize that echo isn't going to search, but this was a simple test.
What is the simplest way to escape periods in arguments to a bash alias?
What you want is a function, and you can use bash's builtin replacement syntax:
$ function tryme() { echo "${1//./\.}"; }
$ tryme 1.1.1.1
1\.1\.1\.1
$ tryme "also. with ... spaces"
also\. with \.\.\. spaces
This will avoid you from forking a sed process.
According to §6.6 "Aliases" of the Bash Reference Manual:
There is no mechanism for using arguments in the replacement text, as in csh. If arguments are needed, a shell function should be used (see Shell Functions).
Also, sed "s/$argv[1]/\\\\./g" wouldn't really make sense anyway, if it put the argument in the sed pattern rather than in the input string.
So, you would write:
function tryme() {
echo "$(echo "$1" | sed "s/\./\\\\./g")"
}
or, using <<< to pass in the input:
function tryme() {
echo "$(sed "s/\./\\\\./g" <<<"$1")"
}

Bash:Single Quotes and Double Quotes and Exclamation Mark

I have a simple script named example:
#!/bin/sh
echo $'${1}'
Please note that the usage of $'' here is to convert \n into new line.
${1} is the first parameter passed to this shell script.
I want to pass a parameter to this script example and it prints the following:
#1. You're smart!
#2. It's a difficult question!
I tried the following:
example "#1. You're smart!\n#2. It's a difficult question!"
An error: -bash: !\n#2.: event not found
Then I tried to escape ! by single quote, and tried:
example '#1. You're smart\!\n#2. It's a difficult question\!'
It outputs:
${1}
Any solution here? Thanks a lot!
$ cat t.sh
#! /bin/bash
echo -e $#
Or echo -e $1, or echo -e ${1} if you just want to process the first argument.
To get bash to stop trying to expand !, use set +H (see In bash, how do I escape an exclamation mark?)
$ set +H
$ ./t.sh "#1. You're smart!\n#2. It's a difficult question!"
#1. You're smart!
#2. It's a difficult question!
What's inside a $'' expression has to be a literal. You can't expand other variables inside it.
But you can do this:
echo "${1//\\n/$'\n'}"
Jan Hudec has an even better answer:
echo -e "$1"

Resources