A huge thank you goes out to Andrew, his blog, and his Git here! His scripts were a huge help getting unixODBC and Python 3 talking via the Microsoft ODBC Driver 11 for SQL Server – RedHat Linux on Ubuntu 14.04.
A couple of quick notes:
- The unixODBC script build_dm.sh will download the needed tarball for you.
- Make sure you download the latest driver from the Microsoft downloads site, the required driver for the scripts will have msodbcsql-11.0.2270.0. Download msodbcsql-11.0.2270.0.tar.gz (Microsoft’s driver) to your /opt directory. The install.sh script Andrew provides is meant to replace the install.sh script in the parent directory of the tarball downloaded from Microsoft to allow for installation on Ubuntu.
$ sudo wget http://download.microsoft.com/download/B/C/D/BCDD264C-7517-4B7D-8159-C99FC5535680/RedHat6/msodbcsql-11.0.2270.0.tar.gz
- The SQL driver and the install.sh script require the below dependencies on Debian based distros to function and make the driver:
$ sudo apt-get install openssl libkrb5-3 libc6 e2fsprogs libodbc1
- If you use the native driver from Microsoft, you do not need to use FreeTDS which is commonly found when Googling this topic. FreeTDS does not support all capabilities of the newer SQL Server versions like the native driver does.
- Andrew’s script for install.sh REQUIRES editing to check for correct packages if running on a Debian based distro other than Debian or Ubuntu, i.e. Linux Mint:
if [ $os_dist_id == "LinuxMint" ]
- You may need to create symbolic links too if installing on a different distro. Linux Mint required all of the links to be created below as I would receive file not found without them:
sudo ln -s /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 /usr/lib/x86_64-linux-gnu/libcrypto.so.10 sudo ln -s /lib/x86_64-linux-gnu/libssl.so.1.0.0 /usr/lib/x86_64-linux-gnu/libssl.so.10 sudo ln -s /usr/lib/x86_64-linux-gnu/libodbcinst.so.2.0.0 /usr/lib/x86_64-linux-gnu/libodbcinst.so.1 sudo ln -s /usr/lib/x86_64-linux-gnu/libodbc.so.2.0.0 /usr/lib/x86_64-linux-gnu/libodbc.so.1
- For the driver installer (install.sh) install, verify (Used to check if everything works), –force (Useful for forcing reinstall with: install –force), and –help are available parameters.
- Make your life 100x easier and use SQL Authentication on the SQL server. If you have to use Kerberos, here is your reference, good luck! 😉
- Connection string parameters for your database can be found here. pyodbc references can be found here, here, and here.
- I’m using pypyodbc not pyodbc (Another package) in my Python below. pypyodbc is referenced here and is compatible with pyodbc.
Once unixODBC is installed, a handy tool for checking out the config files:
$ sudo odbcinst -j unixODBC 2.3.2 DRIVERS............: /etc/odbcinst.ini SYSTEM DATA SOURCES: /etc/odbc.ini FILE DATA SOURCES..: /etc/ODBCDataSources USER DATA SOURCES..: /root/.odbc.ini SQLULEN Size.......: 8 SQLLEN Size........: 8 SQLSETPOSIROW Size.: 8
Sample odbcinst.ini (The script will create this for you!):
$ cat /etc/odbcinst.ini [ODBC Driver 11 for SQL Server] Description=Microsoft ODBC Driver 11 for SQL Server Driver=/opt/microsoft/msodbcsql/lib64/libmsodbcsql-11.0.so.2270.0 Threading=1 UsageCount=2
Sample connection string from Python using pypyodbc:
import pypyodbc
# APP is not required, but helpful if debugging connections from SQL server
cnxn = pypyodbc.connect('APP=YourAppsName;DRIVER={ODBC Driver 11 for SQL Server};SERVER=IPAddress;PORT=1433;DATABASE=DatabaseName;UID=user;PWD=password;')
cursor = cnxn.cursor()
cursor.execute("select * from table")
rows = cursor.fetchall()
Andrew’s build_dm.sh (In case he ever takes it down! 😉 ):
#!/bin/bash
# Microsoft SQL Server ODBC Driver V1.0 for Linux Build unixODBC DriverManager script
# Copyright Microsoft Corp.
# driver name
driver_name="Microsoft SQL Server ODBC Driver V1.0 for Linux"
# required constants
req_os="Linux";
req_proc="x86_64";
req_software=( "wget" "tar" "make" )
# Create a temp directory for intermediate files
tmp=${TMPDIR-/tmp}
tmp=$tmp/"unixODBC".$RANDOM.$RANDOM.$RANDOM
(umask 077 && mkdir $tmp) || {
echo "Could not create temporary directory for the log file." 2>&1
exit 1
}
# driver manager constants
dm_name="unixODBC 2.3.2 DriverManager"
dm_dir="unixODBC-2.3.2"
dm_package='unixODBC-2.3.2.tar.gz'
dm_url="ftp://ftp.unixodbc.org/pub/unixODBC/$dm_package"
dm_package_path=$tmp/$dm_package
dm_build_msg=""
libdir='/usr/lib64'
prefixdir='/usr'
sysconfdir='/etc'
log_file=$tmp/build_dm.log
# warning accepted by user or overridden by command line option
warning_accepted=0
function log()
{
local msg=$*;
local date=$(date);
echo "["$date"]" $msg >> $log_file
}
# format a message and status for an 80 character terminal
# this assumes the msg has already been output and this used
# only for printing the status correctly
function echo_status_aligned
{
local msg=$1
local status=$2
# 2 spaces in between the status and the message
local total_len=$(( ${#msg} + ${#status} + 2 ))
if [ $total_len -gt 80 ]; then
echo "Cannot show a message longer than 80 characters"
exit 1
fi
local dots="................................................................................"
local dot_count=$(( 80 - $total_len ))
local status_msg=" $(expr substr "$dots" 1 $dot_count) $status"
echo $status_msg
return 0
}
# verify that the installation is on a 64 bit OS
function check_for_Linux_x86_64 ()
{
log "Verifying if on a 64 bit Linux compatible OS"
local proc=$(uname -p);
if [ $proc != $req_proc ]; then
log "This installation of the" $dm_name "may only be installed"
log "on a 64 bit Linux compatible operating system."
return 1;
fi
local os=$(uname -s);
if [ $os != $req_os ]; then
log "This installation of the" $dm_name "may only be installed"
log "on a 64 bit Linux compatible operating system."
return 1;
fi
return 0;
}
function check_wget
{
log "Checking that wget is installed"
# if using a file url, wget is not necessary
if [ "${dm_url##file://}" == "$dm_url" ]; then
return 0;
fi
hash wget &> /dev/null
if [ $? -eq 1 ]; then
log "'wget' required to download $dm_name"
return 1;
fi
return 0
}
function check_tar
{
log "Checking that tar is installed"
hash tar &> /dev/null
if [ $? -eq 1 ]; then
log "'tar' required to unpack $dm_name"
return 1;
fi
return 0
}
function check_make
{
log "Checking that make is installed"
hash make &> /dev/null
if [ $? -eq 1 ]; then
log "'make' required to build $dm_name"
return 1;
fi
return 0
}
function download
{
log "Downloading $dm_url"
# if they use a file:// url then just point the package at that path and return
# since wget doesn't support file urls
if [ ${dm_url##file://} != $dm_url ]; then
dm_package_path=${dm_url##file://}
dm_dir=`tar tzf $dm_package_path | head -1 | sed -e 's/\/.*//'`
dm_name="Custom unixODBC $dm_dir"
log "Using $dm_name"
make_build_msg
return 0
fi
$(wget -a $log_file -P $tmp $dm_url )
if [ ! -e $dm_package_path ]; then
log "Failed to retrieve $dm_name from $dm_url."
return 1;
fi
return 0
}
function unpack
{
log "Unpacking $dm_package_path to $tmp"
$(tar --directory=$tmp -xvzf $dm_package_path >> $log_file 2>&1)
if [ $? -ne 0 ]; then
log "Unpacking $dm_package_path failed."
return 1
fi
return 0
}
function configure_dm
{
log "Configuring"
# As per https://msdn.microsoft.com/en-US/library/hh568449(v=sql.110).aspx
# we set this here rather than at the top to delay the eval of
# the variables in the string
local config_options=(
"--enable-gui=no"
"--enable-drivers=no"
"--enable-iconv"
"--with-iconv-char-enc=UTF8"
"--with-iconv-ucode-enc=UTF16LE"
"--libdir=$libdir"
"--prefix=$prefixdir"
"--sysconfdir=$sysconfdir"
"CPPFLAGS=-DSIZEOF_LONG_INT=8"
)
$(cd $tmp/$dm_dir >> $log_file 2>&1; ./configure ${config_options[@]} >> $log_file 2>&1)
if [ $? -ne 0 ]; then
log "Failed to configure $dm_name"
return 1
fi
return 0
}
function make_dm
{
log "Building"
$(cd $tmp/$dm_dir >> $log_file 2>&1 ; make >> $log_file 2>&1)
if [ $? -ne 0 ]; then
log "Failed to make $dm_name"
return 1
fi
return 0
}
function install_dm
{
log "Installing"
$(cd $tmp/$dm_dir >> $log_file 2>&1 ; make install >> $log_file 2>&1)
if [ $? -ne 0 ]; then
log "Failed to make install $dm_name"
return 1
fi
return 0
}
function make_build_msg
{
dm_build_msg=(
"Verifying processor and operating system"
"Verifying wget is installed"
"Verifying tar is installed"
"Verifying make is installed"
"Downloading $dm_name"
"Unpacking $dm_name"
"Configuring $dm_name"
"Building $dm_name"
"Installing $dm_name"
)
}
function build
{
local build_steps=( check_for_Linux_x86_64 check_wget check_tar check_make download unpack configure_dm make_dm install_dm )
make_build_msg
local build_neutral=( "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" )
local build_success=( 'OK' 'OK' 'OK' 'OK' 'OK' 'OK' 'OK' 'OK' 'OK' )
local build_fail=( 'FAILED' 'FAILED' 'FAILED' 'FAILED' 'FAILED' 'FAILED' 'FAILED' 'FAILED' 'FAILED' )
# asserts for the arrays above
if [ ${#build_steps[@]} -ne ${#dm_build_msg[@]} ]; then
echo "Build steps and build message array out of sync"
exit 1
fi
if [ ${#build_steps[@]} -ne ${#build_neutral[@]} ]; then
echo "Build steps and build message array out of sync"
exit 1
fi
if [ ${#build_steps[@]} -ne ${#build_success[@]} ]; then
echo "Build steps and build message array out of sync"
exit 1
fi
if [ ${#build_steps[@]} -ne ${#build_fail[@]} ]; then
echo "Build steps and build message array out of sync"
exit 1
fi
local status=0
for (( i = 0; i < ${#build_steps[@]}; i++ ))
do
local fn=${build_steps[$i]}
local status_msg="${build_neutral[$i]}"
echo -n "${dm_build_msg[$i]} "
if [ $status -eq 0 ]; then
$fn
if [ $? -ne 0 ]; then
status_msg="${build_fail[$i]}"
status=1
else
status_msg="${build_success[$i]}"
fi
fi
echo_status_aligned "${dm_build_msg[$i]} " "$status_msg"
done
return $status
}
function print_usage
{
echo "Usage: build_dm.sh [options]"
echo
echo "This script downloads, configures, builds and installs $dm_name"
echo
echo "Valid options are --help, --download-url, --prefix, --libdir, --sysconfdir, --accept-warning"
echo " --help - prints this message"
echo " --download-url=url | file:// - Specify the location (and name) of unixODBC-2.3.0.tar.gz."
echo " For example, if unixODBC-2.3.0.tar.gz is in the current directory, specify "
echo " --download-url=file://unixODBC-2.3.0.tar.gz."
echo " --prefix - directory to install $dm_package to."
echo " --libdir - directory where ODBC drivers will be placed"
echo " --sysconfdir - directory where $dm_name configuration files are placed"
echo " --accept-warning - indicate that you have read and accept the warning to download the file"
echo
# prevent the script from continuing
exit 0
}
function approve_download
{
log "Accept the WARNING about download of unixODBC"
if [ ! -f "./WARNING" ]; then
log "WARNING file not found."
echo "Cannot display download warning. Please refer to the original archive for the"
echo "WARNING file and then use the --accept-warning option to run this script."
exit 1
fi
hash more &> /dev/null
if [ $? -ne 0 ]; then
log "more program not found. Cannot display the build warning without more."
echo "Cannot display license agreement. Please read the license agreement in LICENSE and"
echo "re-run the install with the --accept-license parameter."
exit 1
fi
more ./WARNING
echo
read -p "Enter 'YES' to have this script continue: " accept
echo
if [ "$accept" == "YES" ]; then
log "Warning accepted"
warning_accepted=1
return 0
fi
log "Warning not accepted"
echo "Exiting because warning not accepted"
exit 1
}
echo
echo "Build and Install $dm_name script"
echo "Copyright Microsoft Corp."
echo
while [ "$1" ]
do
case "$1" in
--download-url=*)
dm_url=${1##--download-url=}
log "$dm_name URL: $dm_url"
;;
--prefix=*)
prefixdir=${1##--prefix=}
log "Installing $dm_name to $prefixdir"
;;
--libdir=*)
libdir=${1##--libdir=}
log "Drivers configured to be placed at $libdir"
;;
--sysconfdir=*)
sysconfdir=${1##--sysconfdir=}
log "Configuration directory set to $sysconfdir"
;;
--help)
print_usage
;;
--accept-warning)
warning_accepted=1
;;
*)
echo "Unknown option $1"
print_usage
exit 1
;;
esac
shift
done
echo "PLEASE NOTE THAT THIS WILL POTENTIALLY INSTALL THE NEW DRIVER MANAGER OVER ANY"
echo "EXISTING UNIXODBC DRIVER MANAGER. IF YOU HAVE ANOTHER COPY OF UNIXODBC INSTALLED,"
echo "THIS MAY POTENTIALLY OVERWRITE THAT COPY."
echo
read -p "Would you like to proceed? (YES/NO): " accept
echo
if [ "$accept" == "YES" ]; then
log "Accepted overwrite warning."
else
log "Declined overwrite warning."
echo "The script is now ending and no actions will be taken."
echo
exit 0
fi
if [ $warning_accepted -ne 1 ]; then
approve_download
fi
build $*
if [ $? -ne 0 ]; then
echo "Errors occurred. See the $log_file file for more details."
exit 1
fi
echo "Successfully installed $dm_name. Please see $log_file for additional information."
exit 0
Andrew’s install.sh (Pretty much what someone at Microsoft should copy to support Ubuntu!):
#!/bin/bash
# Microsoft ODBC Driver 11 for SQL Server Installer
# Copyright Microsoft Corp.
# Set to 1 for debugging information/convenience.
debug=0
# Strings listed here
driver_name="Microsoft ODBC Driver 11 for SQL Server";
driver_version="11.0.2270.0"
driver_dm_name="ODBC Driver 11 for SQL Server"
driver_short_name="msodbcsql"
# Requirements listed here
req_os="Linux";
req_proc="x86_64";
req_dm_ver="2.3.2";
dm_name="unixODBC $req_dm_ver";
os_dist_id=`lsb_release -is`
req_libs=""
if [ $os_dist_id == "Ubuntu" ] || [ $os_dist_id == "Debian" ]; then
req_libs=( '~i"^libc6$"' '~i"libkrb5\-[0-9]$"' '~i"^e2fsprogs$"' '~i"^openssl$"' )
else
req_libs=( glibc e2fsprogs krb5-libs openssl )
fi
#language of the install
lang_id="en_US";
# files to be copied by directory
driver_file="libmsodbcsql-11.0.so.2270.0"
lib_files=( "lib64/$driver_file" )
lib_perms=( 0755 )
bin_files=( "bin/bcp-11.0.2270.0"
"bin/sqlcmd-11.0.2270.0" )
bin_sym=( bcp sqlcmd )
bin_perms=( 0755 0755 )
sup_files=( install.sh build_dm.sh README LICENSE WARNING )
sup_perms=( 0755 0755 0644 0644 0644 )
rll_files=( "bin/bcp.rll"
"bin/SQLCMD.rll"
"bin/BatchParserGrammar.dfa"
"bin/BatchParserGrammar.llr"
"lib64/msodbcsqlr11.rll" )
rll_perms=( 0644 0644 0644 0644 0644 )
doc_files=( "docs/en_US.tar.gz" )
doc_perms='$(printf "0644 %.0s" {1..'${#doc_files[@]}'})'
doc_perms=( $(eval "echo $doc_perms") )
inc_files=( "include/msodbcsql.h" )
inc_perms='$(printf "0644 %.0s" {1..'${#inc_files[@]}'})'
inc_perms=( $(eval "echo $inc_perms") )
dirs=( bin_dir lib_dir sup_dir rll_dir doc_dir inc_dir )
sym_dirs=( bin_sym_dir lib_sym_dir sup_sym_dir rll_sym_dir doc_sym_dir inc_sym_dir )
file_sets=( bin_files lib_files sup_files rll_files doc_files inc_files )
link_sets=( bin_sym "null" "null" "null" "null" "null" )
file_perm_sets=( bin_perms lib_perms sup_perms rll_perms doc_perms inc_perms )
link_perm_sets=( bin_perms lib_perms sup_perms rll_perms doc_perms inc_perms )
# "assertions" that the file and sym link arrays are sane
if [ ${#lib_files[@]} -ne ${#lib_perms[@]} ]; then
echo "Lib files and permission sets don't match"
exit 1;
fi
if [ ${#bin_files[@]} -ne ${#bin_sym[@]} ]; then
echo "Bin files and sym links don't match"
exit 1;
fi
if [ ${#bin_files[@]} -ne ${#bin_perms[@]} ]; then
echo "Bin files and permission sets don't match"
exit 1;
fi
if [ ${#sup_files[@]} -ne ${#sup_perms[@]} ]; then
echo "Supplemental files and permission sets don't match"
exit 1;
fi
if [ ${#rll_files[@]} -ne ${#rll_perms[@]} ]; then
echo "RLL files and permission sets don't match"
exit 1;
fi
if [ ${#dirs[@]} -ne ${#file_sets[@]} ]; then
echo "Directories and file sets don't match"
exit 1;
fi
if [ ${#file_sets[@]} -ne ${#link_sets[@]} ]; then
echo "File and link sets don't match"
exit 1;
fi
if [ ${#file_sets[@]} -ne ${#file_perm_sets[@]} ]; then
echo "File and permission sets don't match"
exit 1;
fi
if [ ${#link_sets[@]} -ne ${#link_perm_sets[@]} ]; then
echo "Link and permission sets don't match"
exit 1;
fi
if [ ${#doc_files[@]} -ne ${#doc_perms[@]} ]; then
echo "Doc files and permission sets don't match"
exit 1;
fi
if [ ${#inc_files[@]} -ne ${#inc_perms[@]} ]; then
echo "Include files and permission sets don't match"
exit 1;
fi
# directories to hold the file categories
bin_dir="";
lib_dir="";
sup_dir="/opt/microsoft/$driver_short_name/$driver_version";
rll_dir="";
doc_dir="";
inc_dir="";
# Force installation flag (--force parameter) default is not to force
force=0
# Accept the license flag (--accept-license) default is that they must accept the license via a prompt
license_accepted=0
# Log file in the temp directory
tmp=${TMPDIR-/tmp}
tmp="$tmp/$driver_short_name.$RANDOM.$RANDOM.$RANDOM"
(umask 077 && mkdir $tmp) || {
echo "Could not create temporary directory for the log file." 2>&1
exit 1
}
log_file=$tmp/install.log
# for debugging purposes
[ $debug -eq 1 ] && log_file="install.log"
[ $debug -eq 1 ] && rm -f install.log
echo
echo "$driver_name Installation Script"
echo "Copyright Microsoft Corp."
echo
echo "Starting install for $driver_name"
echo
function print_usage()
{
echo "Usage: install.sh [global options] command [command options]"
echo
echo "Global options:"
echo " --help - prints this message"
echo "Valid commands are verify and install"
echo " install) install the driver (also verifies before installing and registers"
echo " with the driver manager)"
echo " verify) check to make sure the unixODBC DriverManager configuration is"
echo " correct before installing"
echo "install command take the following options:"
echo " --bin-dir=<directory> - location to create symbolic links for bcp and sqlcmd utilities,"
echo " defaults to the /usr/bin directory"
echo " --lib-dir=<directory> - location to deposit the Microsoft SQL Server ODBC Driver for Linux,"
echo " defaults to the /opt/microsoft/msodbcsql/lib directory"
echo " --force - continues installation even if an error occurs"
echo " --accept-license - forgoes showing the EULA and implies agreement with its contents"
echo
# don't return if we're printing the usage
exit 0;
}
function log()
{
local msg=$*;
local date=$(date);
echo "["$date"]" $msg >> $log_file
}
# format a message and status for an 80 character terminal
function format_status
{
local msg=$1
local status=$2
# 2 spaces in between the status and the message
local total_len=$(( ${#msg} + ${#status} + 2 ))
if [ $total_len -gt 80 ]; then
echo "Cannot show a message longer than 80 characters"
exit 1
fi
local dots="................................................................................"
local dot_count=$(( 80 - $total_len ))
local full_msg="$msg $(expr substr "$dots" 1 $dot_count) $status"
echo $full_msg
return 0
}
function report_config()
{
format_status "Checking for 64 bit Linux compatible OS" "$1"
format_status "Checking required libs are installed" "$2"
format_status "unixODBC utilities (odbc_config and odbcinst) installed" "$3"
format_status "unixODBC Driver Manager version $req_dm_ver installed" "$4"
format_status "unixODBC Driver Manager configuration correct" "$5"
format_status "$driver_name already installed" "$6"
}
# verify that the installation is on a 64 bit OS
function check_for_Linux_x86_64 ()
{
log "Verifying on a 64 bit Linux compatible OS"
local proc=$(uname -p);
if [ $proc != $req_proc ]; then
log "This installation of the $driver_name may only be installed"
log "on a 64 bit Linux compatible operating system."
return 1;
fi
local os=$(uname -s);
if [ $os != $req_os ]; then
log "This installation of the $driver_name may only be installed"
log "on a 64 bit Linux compatible operating system."
return 1;
fi
return 0;
}
# verify that the required libs are on the system
function check_required_libs
{
log "Checking that required libraries are installed"
for lib in ${req_libs[@]}
do
hash rpm &> /dev/null
has_rpm=$?
hash aptitude &> /dev/null
has_aptitude=$?
local present=""
if [ $has_rpm -eq 0 ]; then
present=$(rpm -q -a $lib) >> $log_file 2>&1
elif [ $has_aptitude -eq 0 ]; then
present=$(aptitude search $lib ) >> $log_file 2>&1
fi
if [ "$present" == "" ]; then
log "The $lib library was not found installed in the RPM database."
log "See README for which libraries are required for the $driver_name."
return 1;
fi
done
return 0;
}
# verify that the driver manager utilities are runnable so we may
# check the configuration and install the driver.
function find_odbc_config ()
{
log "Verifying if unixODBC is present"
# see if odbc_config is installed
hash odbc_config &> /dev/null
if [ $? -eq 1 ]; then
log "odbc_config from unixODBC was not found. It is required to properly install the $driver_name";
return 1;
fi
hash odbcinst &> /dev/null
if [ $? -eq 1 ]; then
log "odbcinst from unixODBC was not found. It is required to properly install the $driver_name";
return 1;
fi
return 0;
}
function is_already_installed()
{
log "Checking if $driver_name is already installed in $dm_name"
odbcinst -q -d -n "$driver_dm_name" -v >> $log_file
if [ $? -eq 0 ]; then
log "The $driver_name is already installed."
log "Use --force to reinstall the driver again.";
return 1;
fi
return 0;
}
function verify_dm_version ()
{
log "Verifying that unixODBC is version $req_dm_ver"
# verify version
local version=$(odbc_config --version);
if [ $? -ne 0 ]; then
log "Cannot determine version of installed unixODBC.";
return 1;
fi
local maj_ver_num=`echo $version | cut -d'.' -f1`
local min_ver_num=`echo $version | cut -d'.' -f2`
local pat_ver_num=`echo $version | cut -d'.' -f3`
if [ "$maj_ver_num" -eq "2" ] && [ "$min_ver_num" -ge "3" ] && [ "$pat_ver_num" -ge "0" ]; then
log "unixODBC version is >= 2.3.0";
else
log "unixODBC version must be" $req_dm_ver ". See README for more information.";
return 1;
fi
return 0;
}
function verify_dm_config()
{
local config=$(odbc_config --cflags)
local sizeof_long_int=${config/SIZEOF_LONG_INT\=8/}
local legacy_64bit_mode=${config/BUILD_LEGACY_64_BIT_MODE/}
# configuration must have this flag set, so it should be deleted from sizeof_long_int
if [ "$config" == "$sizeof_long_int" ]; then
log "unixODBC must have the configuration SIZEOF_LONG_INT=8."
log "This will probably require a rebuild of the unixODBC Driver Manager."
log "See README for more information."
return 1;
fi
# configuration shouldn't have this flag set, so it should not be deleted (be the same)
if [ "$config" != "$legacy_64bit_mode" ]; then
log "unixODBC must not have BUILD_LEGACY_64_BIT_MODE configuration flag set."
log "This will probably require a rebuild of the unixODBC Driver Manager."
log "See README for more information."
return 1;
fi
return 0;
}
# verify all configuration prerequisites and print the status of each.
# 0 means all checks pass, 1 means one or more items has failed
function verify_config
{
local proc_os_okay="NOT CHECKED"
local libs_installed="NOT CHECKED"
local odbc_config="NOT CHECKED"
local already_installed="NOT CHECKED"
local version_dm_okay="NOT CHECKED"
local dm_config_okay="NOT CHECKED"
verify_steps=( check_for_Linux_x86_64 check_required_libs find_odbc_config verify_dm_version verify_dm_config is_already_installed )
verify_status_vars=( proc_os_okay libs_installed odbc_config version_dm_okay dm_config_okay already_installed )
verify_success=( 'OK' 'OK' 'OK' 'OK' 'OK*' 'NOT FOUND' )
verify_fail=( 'FAILED' 'NOT FOUND' 'FAILED' 'FAILED' 'FAILED' 'INSTALLED' )
if [ ${#verify_steps[@]} -ne ${#verify_status_vars[@]} ]; then
echo "Error in verify script 1"
exit 1;
fi
if [ ${#verify_steps[@]} -ne ${#verify_success[@]} ]; then
echo "Error in verify script 2"
exit 1;
fi
if [ ${#verify_steps[@]} -ne ${#verify_fail[@]} ]; then
echo "Error in verify script 3"
exit 1;
fi
local i=0
for (( i = 0 ; i < ${#verify_steps[@]} ; i++ ))
do
local fn=${verify_steps[$i]}
local status_var=${verify_status_vars[$i]}
eval $status_var="\"${verify_success[$i]}\""
$fn
if [ $? -ne 0 ]; then
if [ $force -ne 1 ]; then
status=1;
fi
eval $status_var="\"${verify_fail[$i]}\""
break
fi
done
report_config "$proc_os_okay" "$libs_installed" "$odbc_config" "$version_dm_okay" "$dm_config_okay" "$already_installed"
return $status
}
# installation
function copy_files
{
log "Copying files"
local i=0
for (( i = 0 ; i < ${#dirs[@]} ; i++ ))
do
local dir="${dirs[$i]}"
local files="${file_sets[$i]}"
local perms="${file_perm_sets[$i]}"
# evaluate the contents of the variables and assign them back
eval dir=\$$dir
eval files=\(\$\{$files\[\@\]\}\)
eval perms=\(\$\{$perms\[\@\]\}\)
mkdir -p $dir
local j=0
for (( j = 0; j < ${#files[@]}; j++ ))
do
local f=${files[$j]}
local p=${perms[$j]}
log "Copying $f to $dir"
cp $f $dir >> $log_file 2>&1
if [ $? -ne 0 ]; then
log "Failed to copy $f to $dir."
if [ $force -ne 1 ]; then
return 1;
fi
fi
f=${f##*/}
log "Setting permissions on $f"
chmod $p $dir/$f >> $log_file 2>&1
if [ $? -ne 0 ]; then
log "Failed to set the permission of $f to $p."
if [ $force -ne 1 ]; then
return 1;
fi
fi
done
done
return 0;
}
# it tries to remove all the files regardless of the outcome of a removal,
# as such it always returns 0 for "success"
function remove_files
{
log "Removing files"
local i=0
for (( i = 0 ; i < ${#dirs[@]} ; i++ ))
do
local dir="${dirs[$i]}"
local files="${file_sets[$i]}"
local links="${link_sets[$i]}"
# evaluate the contents of the variables and assign them back
eval dir=\$$dir
eval files=\( \$\{$files\[\@\]\} \)
eval links=\( \$\{$links\[\@\]\} \)
for l in ${links[@]}
do
[ "$l" == "null" ] && continue
log "Removing $dir/$l"
rm -f $dir/$l >> $log_file 2>&1
if [ $? -ne 0 ]; then
log "Non fatal error: Failed to remove $l to $dir."
fi
done
for f in ${files[@]}
do
log "Removing $dir/$f"
rm -f $dir/$f >> $log_file 2>&1
if [ $? -ne 0 ]; then
log "Non fatal error: Failed to remove $f to $dir."
fi
done
done
return 0;
}
function register_driver
{
log "Registering the $driver_name driver"
# write INI file for driver installation in the temp directory
local template_ini="$tmp/$driver_short_name.ini"
# for debugging purposes
[ $debug -eq 1 ] && template_ini="$driver_short_name.ini"
echo "[$driver_dm_name]" > $template_ini
echo "Description = $driver_name" >> $template_ini
echo "Driver = $lib_dir/$driver_file" >> $template_ini
echo "Threading = 1" >> $template_ini
echo "" >> $template_ini
if [ $? -ne 0 ]; then
log "Failed to create ini file $template_ini used to install the driver"
return 1;
fi
# install the driver using odbcinst
odbcinst -i -d -f "$template_ini" 2>&1 >> $log_file
if [ $? -ne 0 ]; then
log "Failed installing driver $driver_name with $dm_name"
return 1;
fi
# copy the template ini file to the supplemental directory
cp $template_ini $sup_dir 2>> $log_file
if [ $? -ne 0 ]; then
log "Warning: $template_ini could not be copied to $sup_dir"
fi
return 0;
}
function extract_docs
{
local doc_file="$doc_dir/$lang_id.tar.gz"
log "Extracting documentation from $doc_file"
local cwd=$(pwd)
cd $doc_dir
if [ $? -ne 0 ]; then
log "Couldn't enter the directory $doc_dir to extract documentation."
return 1
fi
tar xvzf $doc_file 2>&1 >> $log_file
if [ $? -ne 0 ]; then
log "Couldn't extract documentation from $doc_file"
return 1
fi
rm $doc_file 2>&1 >> $log_file
if [ $? -ne 0 ]; then
log "Couldn't erase documentation archive after extraction"
return 1
fi
return 0;
}
function create_symlinks
{
log "Creating symbolic links"
local i=0
local j=0
for (( i = 0 ; i < ${#dirs[@]} ; i++ ))
do
local dir="${dirs[$i]}"
local sym_dir="${sym_dirs[i]}"
local files="${file_sets[$i]}"
local links="${link_sets[$i]}"
[ "$links" == "null" ] && continue
# evaluate the contents of the variables and assign them back
eval dir=\$$dir
eval sym_dir=\$$sym_dir
eval files=\( \$\{$files\[\@\]\} \)
eval links=\( \$\{$links\[\@\]\} \)
# assertion that the links and files are the same length
if [ ${#files[@]} -ne ${#links[@]} ]; then
log "Fatal: file and link lists do not match."
exit 1;
fi
# if there is no symbolic link dir, then there are no symlinks
[ "$sym_dir" == "" ] && continue
local j=0
for (( j = 0 ; j < ${#files[@]} ; j++ ))
do
local f="${files[$j]##*/}"
local l="${links[$j]}"
#if the "link" is null, then skip it
[ "$l" == "null" ] && continue
if [ $force -ne 0 ]; then
log "Removing previous link due to force flag"
rm $sym_dir/$l >> $log_file 2>&1
fi
log "Linking $l to $f"
ln -s $dir/$f $sym_dir/$l >> $log_file 2>&1
if [ $? -ne 0 ]; then
log "Failed to link $l to $f."
if [ $force -ne 1 ]; then
return 1;
fi
fi
done
done
# This has been tested in Ubuntu 12.04 and 14.04 LTS
log "Creating symlinks needed in Ubuntu."
hash aptitude &> /dev/null
local has_aptitude=$?
local os_id=$(lsb_release -si);
if [ $has_aptitude -eq 0 ] && [ "$os_id" == "Ubuntu" ]; then
if [ $force -eq 1 ]; then
if [ -h /usr/lib/x86_64-linux-gnu/libcrypto.so.10 ]; then
rm /usr/lib/x86_64-linux-gnu/libcrypto.so.10;
fi
if [ -h /usr/lib/x86_64-linux-gnu/libssl.so.10 ]; then
rm /usr/lib/x86_64-linux-gnu/libssl.so.10;
fi
if [ -h /usr/lib/x86_64-linux-gnu/libodbcinst.so.1 ]; then
rm /usr/lib/x86_64-linux-gnu/libodbcinst.so.1;
fi
if [ -h /usr/lib/x86_64-linux-gnu/libodbc.so.1 ]; then
rm /usr/lib/x86_64-linux-gnu/libodbc.so.1;
fi
fi
ln -s /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 /usr/lib/x86_64-linux-gnu/libcrypto.so.10 >> $log_file 2>&1;
ln -s /lib/x86_64-linux-gnu/libssl.so.1.0.0 /usr/lib/x86_64-linux-gnu/libssl.so.10 >> $log_file 2>&1;
if [ -f /usr/lib/x86_64-linux-gnu/libodbcinst.so.2.0.0 ]; then
ln -s /usr/lib/x86_64-linux-gnu/libodbcinst.so.2.0.0 /usr/lib/x86_64-linux-gnu/libodbcinst.so.1 >> $log_file 2>&1;
else
log "proper libodbcinst.so not found. You will need to create the symlink manually"
fi
if [ -f /usr/lib/x86_64-linux-gnu/libodbc.so.2.0.0 ]; then
ln -s /usr/lib/x86_64-linux-gnu/libodbc.so.2.0.0 /usr/lib/x86_64-linux-gnu/libodbc.so.1 >> $log_file 2>&1;
else
log "proper libodbc.so not found. You will need to create the symlink manually"
fi
else
SCRIPTPATH=$( cd "$(dirname "$0")" ; pwd -P )
log "You need to create some symlinks manually. Use the following command to find out more:"
log "ldd $SCRIPTPATH/lib64/libmsodbcsql-11.0.so.2270.0"
fi
return 0;
}
function report_install()
{
format_status "$driver_name files copied" "$1"
format_status "Symbolic links for bcp and sqlcmd created" "$2"
format_status "$driver_name registered" "$3"
}
function process_params
{
# process parameters
while [ "$1" ]
do
case "$1" in
--force)
force=1
;;
--bin-dir=*)
bin_sym_dir=${1##--bin-dir=}
bin_sym_dir=${bin_sym_dir/#"~"/$HOME}
log "Symbolic links to binaries created in $bin_sym_dir"
;;
--lib-dir=*)
lib_dir=${1##--lib-dir=}
lib_dir=${lib_dir/#"~"/$HOME}
log "Driver directory set to $lib_dir"
;;
--accept-license)
license_accepted=1
log "License agreement accepted"
;;
*)
echo "Unknown parameter $1"
print_usage
exit 1
;;
esac
shift
done
return 0
}
function accept_license
{
log "Accept the license agreement"
if [ ! -f "./LICENSE" ]; then
log "LICENSE file not found."
echo "Cannot display license agreement. Please refer to the original archive for the"
echo "LICENSE file and re-run the install with the --accept-license parameter."
exit 1
fi
hash more &> /dev/null
if [ $? -ne 0 ]; then
log "more program not found. Cannot display license agreement without more."
echo "Cannot display license agreement. Please read the license agreement in LICENSE and"
echo "re-run the install with the --accept-license parameter."
exit 1
fi
more ./LICENSE
echo
read -p "Enter YES to accept the license or anything else to terminate the installation: " accept
echo
if [ "$accept" == "YES" ]; then
log "License agreement accepted"
license_accepted=1
return 0
fi
log "License agreement not accepted"
return 1
}
function install()
{
# return value
local status=0
process_params $*
if [ $license_accepted -ne 1 ]; then
accept_license
fi
# technically accept_license should not return if the license isn't accepted,
# but this is a catch for it
if [ $? -ne 0 ] || [ $license_accepted -ne 1 ]; then
return 1
fi
verify_config
local verified=$?
local files_copied="NOT ATTEMPTED"
local docs_extracted="NOT ATTEMPTED"
local driver_registered="NOT ATTEMPTED"
local symlinks_created="NOT ATTEMPTED"
# if verification passed or force was specified, then call the functions to install
# and register the driver
if [ $verified -eq 0 ] || [ $force -eq 1 ]; then
if [ "$lib_dir" == "" ]; then
lib_dir="/opt/microsoft/$driver_short_name/lib64"
fi
bin_dir="/opt/microsoft/$driver_short_name/bin"
if [ "$bin_sym_dir" == "" ]; then
bin_sym_dir="/usr/bin";
fi
sup_dir="/opt/microsoft/$driver_short_name/$driver_version"
mkdir -p $sup_dir
if [ -d $sup_dir ]; then
log "$sup_dir exists"
else
log "Could not create $sup_dir"
return 1
fi
rll_dir="$sup_dir/$lang_id"
mkdir -p $rll_dir
if [ -d $rll_dir ]; then
log "$rll_dir exists"
else
log "Could not create $rll_dir"
return 1
fi
doc_dir="$sup_dir/docs/$lang_id"
mkdir -p $doc_dir
if [ -d $doc_dir ]; then
log "$doc_dir exists"
else
log "Could not create $doc_dir"
return 1
fi
inc_dir="$sup_dir/include"
mkdir -p $inc_dir
if [ -d $inc_dir ]; then
log "$inc_dir exists"
else
log "Could not create $inc_dir"
return 1
fi
local install_steps=( copy_files extract_docs create_symlinks register_driver )
local install_status_vars=( files_copied docs_extracted symlinks_created driver_registered )
local install_success=( 'OK' 'OK' 'OK' 'INSTALLED' )
local install_fail=( 'FAILED' 'FAILED' 'FAILED' 'FAILED' )
# "assertions" that the variables are sane
if [ ${#install_steps[@]} -ne ${#install_status_vars[@]} ]; then
echo "Error in install script 1"
exit 1;
fi
if [ ${#install_steps[@]} -ne ${#install_success[@]} ]; then
echo "Error in install script 2"
exit 1;
fi
if [ ${#install_steps[@]} -ne ${#install_fail[@]} ]; then
echo "Error in install script 3"
exit 1;
fi
local i=0
for (( i = 0 ; i < ${#install_steps[@]} ; i++ ))
do
local fn=${install_steps[$i]}
local status_var=${install_status_vars[$i]}
eval $status_var="\"${install_success[$i]}\""
$fn
if [ $? -ne 0 ]; then
status=1
eval $status_var="\"${install_fail[$i]}\""
if [ $force -ne 1 ]; then
uninstall
break
fi
fi
done
fi
report_install "$files_copied" "$symlinks_created" "$driver_registered"
return $status
}
function uninstall
{
process_params $*
return 0;
}
command=$1
shift
case "$command" in
install)
install $*
;;
verify)
verify_config $*
;;
--help)
print_usage
;;
*)
echo "Unknown command given."
print_usage
;;
esac
# if any problems, print out a final diagnostic about where to find failure reasons
# and exit with status of 1
if [ $? -ne 0 ]; then
echo
echo "See $log_file for more information about installation failures."
exit 1
fi
echo
echo "Install log created at $log_file."
echo
echo "One or more steps may have an *. See README for more information regarding"
echo "these steps."
# return success
exit 0
I’m happy you found the scripts useful. I’m constantly updating them when users report issues or send pull requests so you may want to use gist-it to embed code snippets directly from the repository.
You don’t have to worry about me deleting the repository, but if you are, you can always fork it to make sure you have a copy for yourself.
Here’s gist-it: http://gist-it.appspot.com
Here’s the repository last updated 6 days ago as of today: https://github.com/Andrewpk/Microsoft–SQL-Server–ODBC-Driver-1.0-for-Linux-Fixed-Install-Scripts
Thanks for linking to the repository and I’m happy you found the scripts useful! Feel free to submit any issues you may have via github.
-Andrew