Would #! /bin/sh - shebang prevent from execution on other operating systems? How to create shell agnostic portable script?
1
vote
2
answers
153
views
If I include a shebang line in my script like following
#! /bin/sh
Would it prevent from execution on other operating systems like Windows which may not have sh in bin folder?
Is this better to omit shebang
line to make a portable script?
How to write a operating system agnostic, shell agnostic, portable script which would execute in the same way in all operating systems or shells?
If the script has to be executed the same way on Mac OS, Windows, Ubuntu, any other Linux or any other operating systems?
---
Assumptions.
I assume to prepare a script for a workshop where workshop participants may use different operating systems and may have different shells; I share preparation notes to install all needed tools like jq
, sed
, sfdx
, curl
and I assume the script would run the same way on all machines and all the operating systems.
I assume workshop participants mainly use Mac OS
, Windows
, Ubuntu
.
Also I assume that participants may install Git Bash
tool on Windows if there is no posix compatible shell.
---
The script itself unwrap2.sh
verbose=$1
# deploy data cloud Data Kit
sf project deploy start -d dc
# unwrap Data Kit components
x=$(sf apex run -f scripts/unwrap.apex --json | jq '.result.logs' -r)
x1=${x#*EXECUTION_STARTED}
x2=${x1#*Result is \(success): }
if [[ "$verbose" = "verbose" ]]; then
echo "x2: $x2"
fi
# Actually this was my mistake here which I have posted in another question
# this line should be x3=$(echo "$x2" | head -n 1)
to make it work
# However, original version of this script with this error didn't have the doublequotes here
x3=$(echo $x2 | head -n 1)
if [[ "$verbose" = "verbose" ]]; then
echo "x3: $x3"
fi
status=$(sf data query -q "select Id,Status FROM BackgroundOperation WHERE Id = '$x3'" --json | jq '.result.records.Status' -r)
if [[ "$verbose" = "verbose" ]]; then
echo "select Id,Status FROM BackgroundOperation WHERE Id = '$x3'"
fi
echo "Status: $status"
while [[ "$status" != "Complete" ]]; do
sleep 10
status=$(sf data query -q "select Id,Status FROM BackgroundOperation WHERE Id = '$x3'" --json | jq '.result.records.Status' -r)
echo "Status: $status"
done
Also, I have other scripts, like setup.sh
email=$1
integrationUsername=$2
defaultFolder="force-app/main/default"
mkdir "$defaultFolder/connectedApps"
cp "template/connectedApps/Integration.connectedApp-meta.xml" "$defaultFolder/connectedApps"
cp "template/data/int.csv" "data"
SEDOPTION=
if [[ "$OSTYPE" == "darwin"* ]]; then
SEDOPTION="-i ''"
fi
# transform connected app from the template folder into default folder
key=${integrationUsername//@/..}
echo "Key: $key"
sed -e "s/{{email}}/$email/g" -e "s/{{integration.username}}/$integrationUsername/g" -e "s/{{key}}/$key/g" "template/connectedApps/Integration.connectedApp-meta.xml" > "$defaultFolder/connectedApps/Integration.connectedApp-meta.xml"
# transform integration user
sed -e "s/{{email}}/$email/g" -e "s/{{integration.username}}/$integrationUsername/g" "template/data/int.csv" > "data/int.csv"
# create integration user
./insertUsers.sh data/int.csv
# deploy connected app
sf project deploy start -d force-app
# assign perm set
sf org assign permset -n GenieAdmin
sf org assign permset -n GenieAdmin -b $integrationUsername
sf org open -p "lightning/setup/SetupOneHome/home?setupApp=audience360"
which invokes insertUsers.sh
data=$(cat $1)
username=$(sfdx force:org:display --json | jq '.result.username' -r)
echo "Username = $username"
echo "Last part is ${username##*.}"
sandbox=${username##*.}
echo "sandbox ${sandbox}"
if [[ $sandbox == 'com' ]]
then
sandbox=${username%%@*}
echo "First part is ${sandbox}"
fi
data=${data//.org/.$sandbox}
echo "$data" > us.csv
sf data upsert bulk -s User -f us.csv -w 500 -i Id --json > upsert.json
echo $(cat upsert.json)
users=$(cat upsert.json | jq '.result.records.successfulResults[].sf__Id' -r)
echo "Users before transformation $users"
users="${users//$'\n'/','}"
echo "Users after transformation $users"
code=$(cat scripts/updateUser.apex)
echo "${code//users/$users}" > temp.apex
sf apex run --file temp.apex
rm temp.apex
rm us.csv
rm upsert.json
IFS="$old_ifs"
Also, I have deploy script ./deploy.sh verbose
verbose=$1
# deploy data cloud Data Kit
sf project deploy start -d dc
# unwrap Data Kit components
scDomain=$(sf org display --json | jq '.result.instanceUrl' -r)
if [[ "$verbose" = "verbose" ]]; then
echo "Sales Cloud domain: $scDomain"
fi
scToken=$(sf org display --json | jq '.result.accessToken' -r)
if [[ "$verbose" = "verbose" ]]; then
echo $scToken
fi
version=$(cat sfdx-project.json | jq '.sourceApiVersion' -r)
result=$(curl --location --request POST "$scDomain/services/data/v$version/actions/custom/flow/sfdatakit__DeployDataKitComponents" \
--header "Authorization: Bearer $scToken" \
--header 'Content-Type: application/json' \
--data @data/unwrap.json
)
figuid=$(echo $result | jq '..outputValues.Flow__InterviewGuid' -r)
if [[ "$verbose" = "verbose" ]]; then
echo "result: $result"
echo "figuid: $figuid"
fi
sf data query -q "SELECT Id, BundleName, ComponentName, ComponentTemplateId, ComponentType, DataKitName, DataSpaceName, DeployJob, DeploymentError, DeploymentStatus , FlowInterviewIdentifier, Name, PublisherOrgComponentId, SubscriberOrgComponentId, TemplateVersion FROM DataKitDeploymentLog WHERE FlowInterviewIdentifier = '$figuid'" --json > soql.json
count=$(cat soql.json | jq '.result.records | length')
if [[ "$verbose" = "verbose" ]]; then
echo "Count: $count"
fi
if [ "$count" = "1" ]; then
x="count is one"
else
x="count is not one"
fi
if [[ "$verbose" = "verbose" ]]; then
echo "x: $x"
fi
while [ "$count" = "1" ]; do
echo "Unwrapping Data Kit components in progress, waiting another 5 seconds..."
sleep 5
sf data query -q "SELECT Id, BundleName, ComponentName, ComponentTemplateId, ComponentType, DataKitName, DataSpaceName, DeployJob, DeploymentError, DeploymentStatus , FlowInterviewIdentifier, Name, PublisherOrgComponentId, SubscriberOrgComponentId, TemplateVersion FROM DataKitDeploymentLog WHERE FlowInterviewIdentifier = '$figuid'" --json > soql.json
count=$(cat soql.json | jq '.result.records | length')
done
deploymentStatus1=$(cat soql.json | jq '.result.records.DeploymentStatus' -r)
deploymentStatus2=$(cat soql.json | jq '.result.records.DeploymentStatus' -r)
if [[ "$verbose" = "verbose" ]]; then
echo "deploymentStatus1: $deploymentStatus1"
echo "deploymentStatus2: $deploymentStatus2"
fi
echo "Unwrapping Data Kit components completed, with statuses $deploymentStatus1 and $deploymentStatus2"
aid=$(sf data query -q "SELECT Id FROM ConnectedApplication WHERE Name = 'Integration'" --json | jq '.result.records.Id' -r)
res=$(curl --location --request GET "$scDomain/$aid" --header "Cookie: sid=$scToken")
r1=${res#*applicationId=}
r2=${r1%\'); *}
echo "r2: $r2"
echo "Open the Connected App View page"
sf org open -p "/app/mgmt/forceconnectedapps/forceAppDetail.apexp?applicationId=$r2"
echo "Trying to open Connected App so that you can click Manage and Copy Consumer Details: Key and Secret"
sf org open -p "/app/mgmt/forceconnectedapps/forceAppManageConsumer.apexp?applicationId=$r2"
# Potentially we could try to follow the Manage Consumer Detail link to parse Key and Secret
# However, it is been redirected, so we would have to fetch the redirection link first
# res=$(curl --location --request GET "$iu/app/mgmt/forceconnectedapps/forceAppManageConsumer.apexp?applicationId=$r2" --header "Cookie: sid=$sid")
# echo $res
# r1=${res#*replace\(\'}
# l1=${r1%\');*}
# l2=${l1#*replace\(\'}
# echo "link: $l2"
# res2=$(curl --location --request GET "$iu/$l2" --header "Cookie: sid=$sid")
#
# which is in $l2 variable, then we would have to figure out how to avoid insufficient permission error
# Currently if you follow this link from UI, it requires MFA for confirmation.
IngestS.sh script
scDomain=$(sf org display --json | jq '.result.instanceUrl' -r)
echo "Sales Cloud domain: $scDomain"
key=$1
secret=$2
verbose=$3
result=$(
curl --location --request POST "$scDomain/services/oauth2/token" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' --data-urlencode "client_id=$key" --data-urlencode "client_secret=$secret"
)
if [[ "$verbose" = "verbose" ]]; then
echo $result
fi
scToken=$(echo $result | jq '.access_token' -r)
if [[ "$verbose" = "verbose" ]]; then
echo $scToken
fi
result=$(curl --location --request POST "$scDomain//services/a360/token" \
--header 'Content-Type: application/x-www-form-urlencoded'\
--data-urlencode 'grant_type=urn:salesforce:grant-type:external:cdp'\
--data-urlencode "subject_token=$scToken"\
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token")
if [[ "$verbose" = "verbose" ]]; then
echo $result
fi
dcToken=$(echo $result | jq '.access_token' -r)
dcDomain=$(echo $result | jq '.instance_url' -r)
if [[ "$verbose" = "verbose" ]]; then
echo $dcToken
echo $dcDomain
fi
result=$(curl --location --request POST "https://$dcDomain/api/v1/ingest/sources/Looker/treatments_statuses/actions/test " \
--header "Authorization: Bearer $dcToken" \
--header 'Content-Type: application/json' \
--data @data/dc.json
)
if [[ "$verbose" = "verbose" ]]; then
echo "Ingest results: $result"
fi
result=$(curl --location --request POST "https://$dcDomain/api/v1/ingest/sources/Looker/treatments_statuses " \
--header "Authorization: Bearer $dcToken" \
--header 'Content-Type: application/json' \
--data @data/dc.json
)
echo "Ingest results: $result"
IngestB.sh script
set -e
set -o pipefail
scDomain=$(sf org display --json | jq '.result.instanceUrl' -r)
if [[ "$verbose" = "verbose" ]]; then
echo "Sales Cloud domain: $scDomain"
fi
key=$1
secret=$2
verbose=$3
result=$(
curl --location --request POST "$scDomain/services/oauth2/token" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' --data-urlencode "client_id=$key" --data-urlencode "client_secret=$secret"
)
if [[ "$verbose" = "verbose" ]]; then
echo $result
fi
scToken=$(echo $result | jq '.access_token' -r)
if [[ "$verbose" = "verbose" ]]; then
echo $scToken
fi
result=$(curl --location --request POST "$scDomain//services/a360/token" \
--header 'Content-Type: application/x-www-form-urlencoded'\
--data-urlencode 'grant_type=urn:salesforce:grant-type:external:cdp'\
--data-urlencode "subject_token=$scToken"\
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token")
if [[ "$verbose" = "verbose" ]]; then
echo $result
fi
dcToken=$(echo $result | jq '.access_token' -r)
dcDomain=$(echo $result | jq '.instance_url' -r)
if [[ "$verbose" = "verbose" ]]; then
echo $dcToken
echo $dcDomain
fi
result=$(curl --location --request POST "https://$dcDomain/api/v1/ingest/jobs " \
--header "Authorization: Bearer $dcToken" \
--header 'Content-Type: application/json' \
--data '{"object":"patients", "sourceName":"Looker", "operation":"upsert"}'
)
if [[ "$verbose" = "verbose" ]]; then
echo "Job create request: $result"
fi
jid=$(echo $result | jq '.id' -r)
if [[ "$verbose" = "verbose" ]]; then
echo "jid: $jid"
fi
if [[ "$jid" = "null" ]]; then
error=$(echo $result | jq '.error' -r)
message=$(echo $result | jq '.message' -r)
echo "Process halted because of error: $error with message: $message"
exit 1
fi
result=$(curl --location --request PUT "https://$dcDomain/api/v1/ingest/jobs/$jid/batches " \
--header 'Content-Type: text/csv' \
--header "Authorization: Bearer $dcToken" \
--data-binary @data/dcPat.csv
)
if [[ "$verbose" = "verbose" ]]; then
echo "Data upload results: $result"
fi
result=$(curl --location --request PATCH "https://$dcDomain/api/v1/ingest/jobs/$jid " \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $dcToken" \
--data '{"state" : "UploadComplete"}'
)
if [[ "$verbose" = "verbose" ]]; then
echo "Patch job results: $result"
fi
result=$(curl --location --request GET "https://$dcDomain/api/v1/ingest/jobs/$jid " \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $dcToken"
)
if [[ "$verbose" = "verbose" ]]; then
echo "get job status results: $result"
fi
state=$(echo $result | jq '.state' -r)
echo "state: $state"
while [[ "$state" != "JobComplete" ]]; do
sleep 10
state=$(echo $result | jq '.state' -r)
echo "state: $state"
done
echo "Final state: $state"
Asked by Patlatus
(123 rep)
Jan 14, 2025, 11:09 AM
Last activity: Jan 15, 2025, 11:04 AM
Last activity: Jan 15, 2025, 11:04 AM