How to use array in ash? - ash

For some reason, I can't use bash to build my script, the only way to do it is with ash, I have this sms auto responder script, each reply must be at max 160 chacters long, it looks like this:
#!/bin/sh
reply="this is a reply message that contains so many words so I have to split it to some parts"
array="${reply:0:5} ${reply:5:5} ${reply:10:5} ${reply:15:5} ${reply:20:5}"
for i in $array
do
echo "send sms with the message: $i"
done
but it ends up like this:
send sms with the message: this
send sms with the message: is
send sms with the message: a
send sms with the message: reply
send sms with the message: mess
send sms with the message: age
send sms with the message: t
What I want is something like this:
send sms with the message: this
send sms with the message: is a
send sms with the message: reply
send sms with the message: messa
send sms with the message: ge th
send sms with the message: at co
send sms with the message: ntain
So it splits by the number of characters instead of splitting by words, how can I do that?

Your array is not actually an array, but read http://mywiki.wooledge.org/WordSplitting for more info on that.
ash does not natively support arrays, but you can circumvent that by using set:
reply="this is a reply message that contains so many words so I have to split it to some parts"
set -- "${reply:0:5}" "${reply:5:5}" "${reply:10:5}" "${reply:15:5}" "${reply:20:5}"
for i; do
echo "send sms with the message: $i"
done
-
send sms with the message: this
send sms with the message: is a
send sms with the message: reply
send sms with the message: mess
send sms with the message: age t
There are many alternative solutions to this, here's one of them using fold to do the splitting work for you with the added advantage that it can break on spaces to make the messages a bit more readable (see man fold for more info):
reply="this is a reply message that contains so many words so I have to split it to some parts"
echo "${reply}" | fold -w 10 -s | while IFS= read -r i; do
echo "send sms with the message: $i"
done
-
send sms with the message: this is a
send sms with the message: reply
send sms with the message: message
send sms with the message: that
send sms with the message: contains
send sms with the message: so many
send sms with the message: words so
send sms with the message: I have to
send sms with the message: split it
send sms with the message: to some
send sms with the message: parts

sh and ash, just as bash does, "support" arrays through strings. When you declare a variable as a string with quotes and leave space in-between you can loop through the items in the array with a for loop.
#! /bin/sh
export SOME_VAR="something in between with,a,comma as an example"
for i in $SOME_VAR; do echo "This is an element of the array: $i"; done
And here is the output:
This is an element of the array: something
This is an element of the array: in
This is an element of the array: between
This is an element of the array: with,a,comma
This is an element of the array: as
This is an element of the array: an
This is an element of the array: example

Related

ARM.Template from bash-script. Unterminated string. Expected delimiter:

I am writing a bash-script for uploading certificate from a linux-server to azure keyvault using the "armclient"
I follow this guide on how to use the armclient:
https://blogs.msdn.microsoft.com/appserviceteam/2016/05/24/deploying-azure-web-app-certificate-through-key-vault/
The command i want to perform is this:
ARMClient.exe PUT /subscriptions/<Subscription Id>/resourceGroups/<Server Farm Resource Group>/providers/Microsoft.Web/certificates/<User Friendly Resource Name>?api-version=2016-03-01 "{'Location':'<Web App Location>','Properties':{'KeyVaultId':'<Key Vault Resource Id>', 'KeyVaultSecretName':'<Secret Name>', 'serverFarmId':'<Server Farm (App Service Plan) resource Id>'}}"
I have created a string that populates all the fields required:
putparm=$resolved_armapi" \"{'Location':'$resolved_locationid','Properties':{'KeyVaultId':'$resolved_keyvaultid','KeyVaultSecretName':'$certname','serverFarmId':'$resolved_farmid'}}"\"
When i echo the output of the variable putparm, the result looks as expected (X-ed out names/ids):
/subscriptions/f073334f-240f-4261-9db5-XXXXXXXXXXXXX/resourceGroups/XXXXXXXX/providers/Microsoft.Web/certificates/XXXX-XXXXX-XXXXX?api-version=2016-03-01 "{'Location':'Central US','Properties':{'KeyVaultId':'/subscriptions/f073334f-240f-4261-9db5-XXXXXXXXXXXXX/resourceGroups/XXXXXXXX/providers/Microsoft.KeyVault/vaults/XXXXXXXX','KeyVaultSecretName':'XXXX-XXXXX-XXXXX','serverFarmId':'/subscriptions/f073334f-240f-4261-9db5-XXXXXXXXXXXXX/resourceGroups/XXXXXXXX/providers/Microsoft.Web/serverfarms/ServicePlan59154b1c-XXXX'}}"
When i run armclient put $putparm in the script i get this error:
"error": {
"code": "InvalidRequestContent",
"message": "The request content was invalid and could not be deserialized: 'Unterminated string. Expected delimiter: \". Path '',
line 1, position 21.'." }
But when i take the output of the $putparm variable and run the command "manually" on the server, it works.
I guess its something with the way linux store the variables and that the API is requesting JSON (or something..)
Happy for any help.
The way you define your variable putparam is wrong.
It is likely interpreted as a literal string and not as an object. Note that a simple string, like "hello", is a valid JSON data, but it probably not what is expecting your server.
If you should quote your variable correctly:
putparm="{\"Location\":\"$resolved_locationid\",\"Properties\":{\"KeyVaultId\":\"$resolved_keyvaultid\",\"KeyVaultSecretName\":\"$certname\",\"serverFarmId\":\"$resolved_farmid\"}}"
and use it like this:
armclient put "$resolved_armapi" "$putparm"

How can I configure exim to run a script for each e-mail

I need to run a shell script under exim that can read each incoming e-mail, optionally rewrites them and then continues sending the email.
And ideal solution would be analogous to UNIX pipes:
cat *incoming email* | **some-script.sh** | *back to exim processing*
I want the script to run very early, so that I can for example change the destination address.
Is this possible with Exim, and how?
You can create the router that use pipe transport with your script on the other side of pipe:
begin routers
preprocessor:
driver = accept
condition = if{ !eq{$header_X-Preproceeded}{yes}}
transport = myscript
no_more
. . . . .
begin transports
myscript:
driver = pipe
user = scriptowner
command = /path/to/script --opt1 --opt2
If incoming message does not have X-Preproceeded header or its value doesn't set to yes then message is passed to the myscript transport. That is performed via piping source | script and all the message is passed to the script's stdin. After proceeding if you want to return proceeded message to the exim, you should add the x-Preproceeded: yes header to the message to prevent it to be routed to the next lap. Then you have to submit it via sendmail command.
#!/bin/sh
. . . .
mandatory_empty_line = ''
cat << ENDOFTEXT | /usr/sbin/sendmail -t
$headers
X_Preproceeded: yes
$mandatory_empty_line
$message_body
ENDOFTEXT
Keep in mind that effective user running script should be added to the trusted users by exim's config to allow sending from arbitrary address. Otherwise exim will replace any sender's address by scriptowner#mydomain.tld

AWS SNS how to add line breaks in message

I'm trying to send SNS messages via CLI in json format.
aws sns publish --cli-input-json "{\"TopicArn\":\"xxx\",\"Message\":\"first line\n second line\",\"Subject\":\"Empty subject\"}"
But the \n doesn't work. Neither is "\r\n" or "\n". I think the string is escaped by SNS so \n doesn't work. Does anyone know how to send a message of 2 lines?(Sending 2 messages is not an option) Appreciate your advice!
I think \\n is actually what you are looking for. I've just tested it by sending push notifications to my device through AWS SNS.
So your message should look like this:
aws sns publish --cli-input-json "{\"TopicArn\":\"xxx\",\"Message\":\"first line\\nsecond line\",\"Subject\":\"Empty subject\"}"
Note, you should not leave the white space after the line break symbol, otherwise, your new line would start with that space.
aws sns publish --topic-arn "arn:aws:sns:us-west-2:0123456789012:my-topic" --message file://message.txt
message.txt is a text file containing the message to publish:
Hello World
Second Line
Putting the message in a text file allows you to include line breaks
.
This worked out for me:
"first line
second line"
I am publishing messages using the email protocol using the NodeJs aws-sdk. In order for exceptions to appear correctly, I needed to replace both \n and \\n, and to appease both windows and mac clients, used \r\n.
message.replace(/\n|\\n/g, '\r\n')
For anyone who needs full code, this is how I am handling errors in typescript
public prepareMessage(header: string, error: any) {
const data = (error instanceof Error)
? JSON.stringify(error, Object.getOwnPropertyNames(error), 2)
: JSON.stringify(error, null, 2);
const replaceNewlines = (str: string) => str?.replace(/\n|\\n/g, '\r\n') || '';
return `${replaceNewlines(header)}\r\n${replaceNewlines(data)}`;
}
four backslash
works for me
using Aws SNS with Firebase
EX: backslashbackslashbackslashbackslash+n
After testing all suggested answers, here's what worked in my case (running from a python lambda function, publishing from boto3 sns client):
This created 2 new lines: message.replace('\n', '\r\n')
This created 1 new line: message.replace('\n', '\r')
Example:
message = message.replace('\n', '\r').replace('\t', ' ')
# Sending the notification...
snsclient.publish(
TargetArn=SNS_EMAIL_ALERTS_ARN,
Subject=f'{filter_name} Alert: ({lambda_func_name[3]})',
Message=message
)

EXPECT Script for Cisco / Juniper Config Backup

Firstly I am asking this as a beginner in scripting, currently I have an expect script which automatically logs in to predefined Cisco devices and runs certain commands, I would like to update my script so that this same script can also backup Juniper devices.
Ideally what I would like the script to do is (in pseudo code)
login / send credentials
expect prompt
send command "show version"
if output contains "JUNOS" then
send command 1
send command 2
send command 3
otherwise if output contains "Cisco" then
send command 1
send command 2
send command 3
Im sorry if this has been asked before, but I have tried searching and searching and couldn't find an answer if anyone can assist with this I would really appreciate it. I have also included my current expect script for your reference (this script gets called by a BASH Script)
set timeout 5
set hostname [lindex $argv 0]
set username "user"
set password "pass"
spawn ssh $username#$hostname
expect "Password:"
send "$password\n"
expect "#" {
send "terminal length 0\n"
send "show running-config\n"
expect "end\r"
send "\n"
send "exit\n"
}
---- UPDATE ---
Thanks for your input Dinesh - I have updated my script to include what you provided (as below)
set timeout 5
set hostname [lindex $argv 0]
set username "user"
set password "pass"
set prompt "(>|#|\\\$) $"
spawn ssh $username#$hostname
expect "*assword:"
send $password\r
send \r
expect -re $prompt {
send "show version\r"
expect -re $prompt
expect *;
set output $expect_out(buffer);
#Basic string check logic with Tcl
if { [string first "JUNOS" $output ]!=-1 } {
send "show configuration | display set | no-more"
expect -re $prompt
send "exit\r"
} else {
send "terminal length 0\r"
expect -re $prompt
send "show run\r"
expect "end"
send \r
expect -re $prompt
send "exit\r"
}
}
However when i run this script the issue I have is that the output of the "show version" doesn't seem to be matching my "string check" and the script therefore ignores the "if" statement and proceeds with the "else" statement.
The output of the "show version" command is below - what will I need to modify so that the "JUNOS" string gets matched?
user#host> show version
Hostname: host
Model: srx240h
JUNOS Software Release [11.4R7.5]
--- EDIT 2: Output from the script
05c4rw#testpc:~/script$ ./ssh.sh
spawn ssh user#juniperhost
## LOGIN BANNER - Removed for brevity
user#juniperhost's password:
--- JUNOS 11.4R7.5 built 2013-03-01 11:40:03 UTC
user#juniperhost> show version
Hostname: juniperhost
Model: srx240h
JUNOS Software Release [11.4R7.5]
user#juniperhost> show configuration | display set | no-more
set version 11.4R7.5
## *** OUTPUT REMOVED FOR BREVITY / PRIVACY ***
## *** END OF OUTPUT from previous command
user#juniper> spawn ssh user#ciscohost
password:
## LOGIN BANNER - removed for brevitiy
ciscohost#05c4rw#testpc:~/script$
set timeout 5
set hostname [lindex $argv 0]
set username "user"
set password "pass"
spawn ssh $username#$hostname
expect "Password:"
send "$password\r"
expect "#" {
send "terminal length 0\r"
expect "#"
# This is to clean up the previous expect_out(buffer) content
# So that, we can get the exact output what we need.
expect *;
send "show running-config\r"
expect "end"
#Now, the content of 'expect_out(buffer)' has the whole 'show run' output
set output $expect_out(buffer);
#Basic string check logic with Tcl
if { [string first "JUNOS" $output ]!=-1 } {
# Apply your logic here
# send "command1"
# expect "#"
} else {
# Same as above
# I assume, there are 2 possibilities. So, keeping 'else' part alone.
# Have 'else if', if you have more than 2.
}
}
Notice that each line sent by the script is terminated with \r. This denotes a return character and is exactly what you would press if you entered these lines at the shell, so that is exactly what Expect has to send. It is a common mistake to terminate send commands to a process followed by \n. In this context, \n denotes a linefeed character. You do not interactively end lines with a linefeed. So Expect must not either. So, always use \r.
You can have a look at here if you are interested to know more about the why you need expect *. (which is a separate story)
I can see that there are some commands used only with send in your example. Basically, expect will work with two feasible commands such as send and expect. In this case, if send is used, then it is mandatory to have expect (in most of the cases) afterwards. (while the vice-versa is not required to be mandatory)
This is because without that we will be missing out what is happening in the spawned process as expect will assume that you simply need to send one string value and not expecting anything else from the session.
This might not be what you were looking for but thought I will post it just in case.
You might want to look into something like Rancid instead of having those scripts. Rancid will not only backup your device configs, it will also provide you with a diff on the devices it is managing at pre-defined intervals (for e.g if you set the interval to 15 mins, rancid will login to your devices every 15 mins grab the configs, back them up and do a diff with the previous version and show you the diff)

How to store output in a variable while using expect 'send' command

Thanks.
But the account and password are needed. So I must send them and then send ovs-vsctl command.
the scripts are something like this:
spawn telnet#ip
expect -re "*login*" {
send "root"
}
expect -re "password*" {
send "****"
}
send "ovs-vsctl *******"
I want to store the output of this command send "ovs-vsctl ****", but many times I got some output of the command "send "password"", how can I get the output of send "ovs-vsctl****". The output of the command send "ovs-vsctl *** are two string and each string occupies one line.
Thanks.
Maybe:
log_user 0 ;# turn off the usual output
spawn telnet#ip
expect -re "*login*"
send "root\r"
expect -re "password*"
send "****\r"
send "ovs-vsctl *******"
expect eof
puts $expect_out(buffer) ;# print the results of the command
Expect works with an input buffer, which contains everything that is returned from the interactive application, meaning both process output and your input (as long is it is echoed from the remote device, which is usually the case).
The expect command is used to recover text from the input buffer. Each time a match is found, the buffer up to the end of that match is cleared, and saved to $expect_out(buffer). The actual match is saved to $expect_out(0,string). The buffer then resets.
What you need to do in your case is match the output with an expect statement, to get what you want.
What I would do in your case is match the remote device prompt after sending the password, then match it again after command was sent. That way the buffer after the last match will hold the needed output.
Something along the lines of:
[...]
expect -re "password*" {
send "****"
}
expect -re ">"
send "ovs-vsctl *******\r"
expect -re ">" # Better if you can use a regexp based on your knowledge of device output here - see below
puts $expect_out(buffer)
By matching using a regexp based on your knowledge of the output, you should be able to extract only the command output and not the echoed command itself. Or you can always do that after-the-fact, by using the regexp command.
Hope that helps!