How to detect the OS from a Bash script? - bash
I would like to keep my .bashrc and .bash_login files in version control so that I can use them between all the computers I use. The problem is I have some OS specific aliases so I was looking for a way to determine if the script is running on Mac OS X, Linux or Cygwin.
What is the proper way to detect the operating system in a Bash script?
I think the following should work. I'm not sure about win32 though.
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
# ...
elif [[ "$OSTYPE" == "darwin"* ]]; then
# Mac OSX
elif [[ "$OSTYPE" == "cygwin" ]]; then
# POSIX compatibility layer and Linux environment emulation for Windows
elif [[ "$OSTYPE" == "msys" ]]; then
# Lightweight shell and GNU utilities compiled for Windows (part of MinGW)
elif [[ "$OSTYPE" == "win32" ]]; then
# I'm not sure this can happen.
elif [[ "$OSTYPE" == "freebsd"* ]]; then
# ...
else
# Unknown.
fi
For my .bashrc, I use the following code:
platform='unknown'
unamestr=$(uname)
if [[ "$unamestr" == 'Linux' ]]; then
platform='linux'
elif [[ "$unamestr" == 'FreeBSD' ]]; then
platform='freebsd'
fi
Then I do somethings like:
if [[ $platform == 'linux' ]]; then
alias ls='ls --color=auto'
elif [[ $platform == 'freebsd' ]]; then
alias ls='ls -G'
fi
It's ugly, but it works. You may use case instead of if if you prefer.
The bash manpage says that the variable OSTYPE stores the name of the operation system:
OSTYPE Automatically set to a string that describes the operating system on which bash is executing. The default is system-
dependent.
It is set to linux-gnu here.
$OSTYPE
You can simply use pre-defined $OSTYPE variable e.g.:
case "$OSTYPE" in
solaris*) echo "SOLARIS" ;;
darwin*) echo "OSX" ;;
linux*) echo "LINUX" ;;
bsd*) echo "BSD" ;;
msys*) echo "WINDOWS" ;;
cygwin*) echo "ALSO WINDOWS" ;;
*) echo "unknown: $OSTYPE" ;;
esac
However it's not recognized by the older shells (such as Bourne shell).
uname
Another method is to detect platform based on uname command.
See the following script (ready to include in .bashrc):
# Detect the platform (similar to $OSTYPE)
OS="`uname`"
case $OS in
'Linux')
OS='Linux'
alias ls='ls --color=auto'
;;
'FreeBSD')
OS='FreeBSD'
alias ls='ls -G'
;;
'WindowsNT')
OS='Windows'
;;
'Darwin')
OS='Mac'
;;
'SunOS')
OS='Solaris'
;;
'AIX') ;;
*) ;;
esac
You can find some practical example in my .bashrc.
Here is similar version used on Travis CI:
case $(uname | tr '[:upper:]' '[:lower:]') in
linux*)
export TRAVIS_OS_NAME=linux
;;
darwin*)
export TRAVIS_OS_NAME=osx
;;
msys*)
export TRAVIS_OS_NAME=windows
;;
*)
export TRAVIS_OS_NAME=notset
;;
esac
Detecting operating system and CPU type is not so easy to do portably. I have a sh script of about 100 lines that works across a very wide variety of Unix platforms: any system I have used since 1988.
The key elements are
uname -p is processor type but is usually unknown on modern Unix platforms.
uname -m will give the "machine hardware name" on some Unix systems.
/bin/arch, if it exists, will usually give the type of processor.
uname with no arguments will name the operating system.
Eventually you will have to think about the distinctions between platforms and how fine you want to make them. For example, just to keep things simple, I treat i386 through i686 , any "Pentium*" and any "AMD*Athlon*" all as x86.
My ~/.profile runs an a script at startup which sets one variable to a string indicating the combination of CPU and operating system. I have platform-specific bin, man, lib, and include directories that get set up based on that. Then I set a boatload of environment variables. So for example, a shell script to reformat mail can call, e.g., $LIB/mailfmt which is a platform-specific executable binary.
If you want to cut corners, uname -m and plain uname will tell you what you want to know on many platforms. Add other stuff when you need it. (And use case, not nested if!)
I recommend to use this complete bash code
lowercase(){
echo "$1" | sed "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/"
}
OS=`lowercase \`uname\``
KERNEL=`uname -r`
MACH=`uname -m`
if [ "{$OS}" == "windowsnt" ]; then
OS=windows
elif [ "{$OS}" == "darwin" ]; then
OS=mac
else
OS=`uname`
if [ "${OS}" = "SunOS" ] ; then
OS=Solaris
ARCH=`uname -p`
OSSTR="${OS} ${REV}(${ARCH} `uname -v`)"
elif [ "${OS}" = "AIX" ] ; then
OSSTR="${OS} `oslevel` (`oslevel -r`)"
elif [ "${OS}" = "Linux" ] ; then
if [ -f /etc/redhat-release ] ; then
DistroBasedOn='RedHat'
DIST=`cat /etc/redhat-release |sed s/\ release.*//`
PSUEDONAME=`cat /etc/redhat-release | sed s/.*\(// | sed s/\)//`
REV=`cat /etc/redhat-release | sed s/.*release\ // | sed s/\ .*//`
elif [ -f /etc/SuSE-release ] ; then
DistroBasedOn='SuSe'
PSUEDONAME=`cat /etc/SuSE-release | tr "\n" ' '| sed s/VERSION.*//`
REV=`cat /etc/SuSE-release | tr "\n" ' ' | sed s/.*=\ //`
elif [ -f /etc/mandrake-release ] ; then
DistroBasedOn='Mandrake'
PSUEDONAME=`cat /etc/mandrake-release | sed s/.*\(// | sed s/\)//`
REV=`cat /etc/mandrake-release | sed s/.*release\ // | sed s/\ .*//`
elif [ -f /etc/debian_version ] ; then
DistroBasedOn='Debian'
DIST=`cat /etc/lsb-release | grep '^DISTRIB_ID' | awk -F= '{ print $2 }'`
PSUEDONAME=`cat /etc/lsb-release | grep '^DISTRIB_CODENAME' | awk -F= '{ print $2 }'`
REV=`cat /etc/lsb-release | grep '^DISTRIB_RELEASE' | awk -F= '{ print $2 }'`
fi
if [ -f /etc/UnitedLinux-release ] ; then
DIST="${DIST}[`cat /etc/UnitedLinux-release | tr "\n" ' ' | sed s/VERSION.*//`]"
fi
OS=`lowercase $OS`
DistroBasedOn=`lowercase $DistroBasedOn`
readonly OS
readonly DIST
readonly DistroBasedOn
readonly PSUEDONAME
readonly REV
readonly KERNEL
readonly MACH
fi
fi
echo $OS
echo $KERNEL
echo $MACH
more examples examples here: https://github.com/coto/server-easy-install/blob/master/lib/core.sh
I would suggest avoiding some of these answers. Don't forget that you can choose other forms of string comparison, which would clear up most of the variations, or ugly code offered.
One such solution would be a simple check, such as:
if [[ "$OSTYPE" =~ ^darwin ]]; then
Which has the added benefit of matching any version of Darwin, despite it's version suffix. This also works for any variations of Linux one may expect.
You can see some additional examples within my dotfiles here
uname
or
uname -a
if you want more information
In bash, use $OSTYPE and $HOSTTYPE, as documented; this is what I do. If that is not enough, and if even uname or uname -a (or other appropriate options) does not give enough information, there’s always the config.guess script from the GNU project, made exactly for this purpose.
Try using "uname". For example, in Linux: "uname -a".
According to the manual page, uname conforms to SVr4 and POSIX, so it should be available on Mac OS X and Cygwin too, but I can't confirm that.
BTW: $OSTYPE is also set to linux-gnu here :)
I wrote these sugars in my .bashrc:
if_os () { [[ $OSTYPE == *$1* ]]; }
if_nix () {
case "$OSTYPE" in
*linux*|*hurd*|*msys*|*cygwin*|*sua*|*interix*) sys="gnu";;
*bsd*|*darwin*) sys="bsd";;
*sunos*|*solaris*|*indiana*|*illumos*|*smartos*) sys="sun";;
esac
[[ "${sys}" == "$1" ]];
}
So I can do stuff like:
if_nix gnu && alias ls='ls --color=auto' && export LS_COLORS="..."
if_nix bsd && export CLICOLORS=on && export LSCOLORS="..."
if_os linux && alias psg="ps -FA | grep" #alternative to pgrep
if_nix bsd && alias psg="ps -alwx | grep -i" #alternative to pgrep
if_os darwin && alias finder="open -R"
Below it's an approach to detect Debian and RedHat based Linux OS making use of the /etc/lsb-release and /etc/os-release (depending on the Linux flavor you're using) and take a simple action based on it.
#!/bin/bash
set -e
YUM_PACKAGE_NAME="python python-devl python-pip openssl-devel"
DEB_PACKAGE_NAME="python2.7 python-dev python-pip libssl-dev"
if cat /etc/*release | grep ^NAME | grep CentOS; then
echo "==============================================="
echo "Installing packages $YUM_PACKAGE_NAME on CentOS"
echo "==============================================="
yum install -y $YUM_PACKAGE_NAME
elif cat /etc/*release | grep ^NAME | grep Red; then
echo "==============================================="
echo "Installing packages $YUM_PACKAGE_NAME on RedHat"
echo "==============================================="
yum install -y $YUM_PACKAGE_NAME
elif cat /etc/*release | grep ^NAME | grep Fedora; then
echo "================================================"
echo "Installing packages $YUM_PACKAGE_NAME on Fedorea"
echo "================================================"
yum install -y $YUM_PACKAGE_NAME
elif cat /etc/*release | grep ^NAME | grep Ubuntu; then
echo "==============================================="
echo "Installing packages $DEB_PACKAGE_NAME on Ubuntu"
echo "==============================================="
apt-get update
apt-get install -y $DEB_PACKAGE_NAME
elif cat /etc/*release | grep ^NAME | grep Debian ; then
echo "==============================================="
echo "Installing packages $DEB_PACKAGE_NAME on Debian"
echo "==============================================="
apt-get update
apt-get install -y $DEB_PACKAGE_NAME
elif cat /etc/*release | grep ^NAME | grep Mint ; then
echo "============================================="
echo "Installing packages $DEB_PACKAGE_NAME on Mint"
echo "============================================="
apt-get update
apt-get install -y $DEB_PACKAGE_NAME
elif cat /etc/*release | grep ^NAME | grep Knoppix ; then
echo "================================================="
echo "Installing packages $DEB_PACKAGE_NAME on Kanoppix"
echo "================================================="
apt-get update
apt-get install -y $DEB_PACKAGE_NAME
else
echo "OS NOT DETECTED, couldn't install package $PACKAGE"
exit 1;
fi
exit 0
Output example for Ubuntu Linux:
delivery#delivery-E5450$ sudo sh detect_os.sh
[sudo] password for delivery:
NAME="Ubuntu"
===============================================
Installing packages python2.7 python-dev python-pip libssl-dev on Ubuntu
===============================================
Ign http://dl.google.com stable InRelease
Get:1 http://dl.google.com stable Release.gpg [916 B]
Get:2 http://dl.google.com stable Release [1.189 B]
...
You can use the following:
OS=$(uname -s)
then you can use OS variable in your script.
I wrote a personal Bash library and scripting framework that uses GNU shtool to do a rather accurate platform detection.
GNU shtool is a very portable set of scripts that contains, among other useful things, the 'shtool platform' command. Here is the output of:
shtool platform -v -F "%sc (%ac) %st (%at) %sp (%ap)"
on a few different machines:
Mac OS X Leopard:
4.4BSD/Mach3.0 (iX86) Apple Darwin 9.6.0 (i386) Apple Mac OS X 10.5.6 (iX86)
Ubuntu Jaunty server:
LSB (iX86) GNU/Linux 2.9/2.6 (i686) Ubuntu 9.04 (iX86)
Debian Lenny:
LSB (iX86) GNU/Linux 2.7/2.6 (i686) Debian GNU/Linux 5.0 (iX86)
This produces pretty satisfactory results, as you can see. GNU shtool is a little slow, so I actually store and update the platform identification in a file on the system that my scripts call. It's my framework, so that works for me, but your mileage may vary.
Now, you'll have to find a way to package shtool with your scripts, but it's not a hard exercise. You can always fall back on uname output, also.
EDIT:
I missed the post by Teddy about config.guess (somehow). These are very similar scripts, but not the same. I personally use shtool for other uses as well, and it has been working quite well for me.
try this:
DISTRO=$(cat /etc/*-release | grep -w NAME | cut -d= -f2 | tr -d '"')
echo "Determined platform: $DISTRO"
This should be safe to use on all distros.
$ cat /etc/*release
This produces something like this.
DISTRIB_ID=LinuxMint
DISTRIB_RELEASE=17
DISTRIB_CODENAME=qiana
DISTRIB_DESCRIPTION="Linux Mint 17 Qiana"
NAME="Ubuntu"
VERSION="14.04.1 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.1 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
Extract/assign to variables as you wish
Note: On some setups. This may also give you some errors that you can ignore.
cat: /etc/upstream-release: Is a directory
You can use following if clause and expand it as needed:
if [ "${OSTYPE//[0-9.]/}" == "darwin" ]
then
aminute_ago="-v-1M"
elif [ "${OSTYPE//[0-9.]/}" == "linux-gnu" ]
then
aminute_ago="-d \"1 minute ago\""
fi
This is what I use if anyone is interested in detecting WSL vs WSL verion 2 as well.
#!/usr/bin/env bash
unameOut=$(uname -a)
case "${unameOut}" in
*Microsoft*) OS="WSL";; #must be first since Windows subsystem for linux will have Linux in the name too
*microsoft*) OS="WSL2";; #WARNING: My v2 uses ubuntu 20.4 at the moment slightly different name may not always work
Linux*) OS="Linux";;
Darwin*) OS="Mac";;
CYGWIN*) OS="Cygwin";;
MINGW*) OS="Windows";;
*Msys) OS="Windows";;
*) OS="UNKNOWN:${unameOut}"
esac
echo ${OS};
I tend to keep my .bashrc and .bash_alias on a file share that all platforms can access. This is how I conquer the problem in my .bash_alias:
if [[ -f (name of share)/.bash_alias_$(uname) ]]; then
. (name of share)/.bash_alias_$(uname)
fi
And I have for example a .bash_alias_Linux with:
alias ls='ls --color=auto'
This way I keep platform specific and portable code separate, you can do the same for .bashrc
I tried the above messages across a few Linux distros and found the following to work best for me. It’s a short, concise exact word answer that works for Bash on Windows as well.
OS=$(cat /etc/*release | grep ^NAME | tr -d 'NAME="') #$ echo $OS # Ubuntu
This checks a bunch of known files to identfy if the linux distro is Debian or Ubunu, then it defaults to the $OSTYPE variable.
os='Unknown'
unamestr="${OSTYPE//[0-9.]/}"
os=$( compgen -G "/etc/*release" > /dev/null && cat /etc/*release | grep ^NAME | tr -d 'NAME="' || echo "$unamestr")
echo "$os"
Doing the following helped perform the check correctly for ubuntu:
if [[ "$OSTYPE" =~ ^linux ]]; then
sudo apt-get install <some-package>
fi
Related
How to detect OS system using BASH?
The script should be able to detect the operating system that is running. The alternatives OS is Arch Linux, Centos and Ubuntu. os=$(uname) if [ "$os" == "Arch" ]; then echo "Arch Linux detected" elif [ "$os" == "CentOS" ]; then echo "CentOS detected" elif [ "$os" == "Ubuntu" ]; then echo "Ubuntu detected" else echo "Unknown OS detected" fi``` Output: Unknown OS detected I tried doing this: \`del1() { os=$(cat /etc/os-release | grep "PRETTY_NAME") } del1 echo "The operating system is: $os"\` The output: PRETTY_NAME="Ubuntu 20.04.2 LTS" But I want to check between Centos, Arch Linux and Ubuntu. Any suggestions?
The uname command will always return Linux when running on Linux, so of course that's never going to work. Using /etc/os-release is probably the best solution, but don't grep it for information; the file is a collection of shell variables that you can source with the . command, so you can write something like this: #!/bin/sh . /etc/os-release case $ID in ubuntu) echo "This is Ubuntu!" ;; arch) echo "This is Arch Linux!" ;; centos) echo "This is CentOS!" ;; *) echo "This is an unknown distribution." ;; esac
You can use the grep command to filter the output of the cat /etc/os-release command for specific strings that indicate the operating system. For example, you could use the following command to check for Ubuntu: os=$(cat /etc/os-release | grep -o "Ubuntu") You could then use an if statement to check if the variable os equals to Ubuntu: if [ "$os" == "Ubuntu" ]; then echo "Ubuntu detected" else echo "Not Ubuntu detected" fi You can do the same to check for Arch Linux: os=$(cat /etc/os-release | grep -o "Arch") And for Centos: os=$(cat /etc/os-release | grep -o "CentOS") You can also use cat /etc/*-release instead of cat /etc/os-release for more general detection of the OS. You can also use lsb_release -a command to get more details about the distribution and version of the OS. os=$(lsb_release -a | grep -o "Ubuntu") You can then create a function that check for each os one by one and print the output accordingly. check_os(){ os=$(cat /etc/os-release | grep -o "Ubuntu") if [ "$os" == "Ubuntu" ]; then echo "Ubuntu detected" else os=$(cat /etc/os-release | grep -o "Arch") if [ "$os" == "Arch" ]; then echo "Arch Linux detected" else os=$(cat /etc/os-release | grep -o "CentOS") if [ "$os" == "CentOS" ]; then echo "CentOS detected" else echo "Unknown OS detected" fi fi fi } check_os Please note that this approach might not be 100% accurate and it is better to use the appropriate package manager commands to check the OS version and distribution.
If you just need to check the name of the OS, you can try; source /etc/os-release echo "The operating system is: $NAME"
bash: why is this command running when the condition hasn't been met? [duplicate]
This question already has answers here: Why equal to operator does not work if it is not surrounded by space? (4 answers) Closed 5 years ago. I am trying to consolidate my dotfiles so I only have one set for my local OSX machine and Linux servers. I have this conditional statement, but the $(brew --prefix) is running on linux machines even though that block shouldn't run... SYSTEM=`uname -a | cut -d" " -f1` # These things are system specific if [ $SYSTEM=="Darwin" ]; then if [ -f $(brew --prefix)/etc/bash_completion ]; then . $(brew --prefix)/etc/bash_completion fi for file in ~/.completion/* ; do if [ -f "$file" ] ; then . "$file" fi done elif [ $SYSTEM=="Linux" ]; then alias upgrade='sudo apt-get update && sudo apt-get upgrade' # save the IP address in case I need it later export IP_ADDRESS=`ifconfig eth0 | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'` fi if I echo the $SYSTEM variable on the linux machine it says it is Linux but sourcing of the bashrc fails on the brew --prefix command. Does it evaluate that variable no matter what? Is there a way I can change it to work?
You need spaces around the ==: if [ $SYSTEM == "Darwin" ] Otherwise bash behaves as if you had written: if [ "some-non-empty-string" ] which is always true, as opposed to: if [ "" ] which is always false.
Shell Script to find the Operating System of the machine
How to find the operating system using bash script? I found this answer: Detect the OS from a Bash script. It is not clear it would work on Mac OS X. I would like to find it on Mac OS X vs different linux OS's.
For Linux you can type in the following bash command: $ cat /etc/*-release For Mac OS X you can try one of these commands: $ sw_vers -productVersion $ system_profiler SPSoftwareDataType
Derived from other answers, this worked for me: CURRENT_OS="OSX" #CENTOS, UBUNUTU are other valid options function findCurrentOSType() { echo "Finding the current os type" echo osType=$(uname) case "$osType" in "Darwin") { echo "Running on Mac OSX." CURRENT_OS="OSX" } ;; "Linux") { # If available, use LSB to identify distribution if [ -f /etc/lsb-release -o -d /etc/lsb-release.d ]; then DISTRO=$(gawk -F= '/^NAME/{print $2}' /etc/os-release) else DISTRO=$(ls -d /etc/[A-Za-z]*[_-][rv]e[lr]* | grep -v "lsb" | cut -d'/' -f3 | cut -d'-' -f1 | cut -d'_' -f1) fi CURRENT_OS=$(echo $DISTRO | tr 'a-z' 'A-Z') } ;; *) { echo "Unsupported OS, exiting" exit } ;; esac }
use uname $(uname -s) this will give you the os name (Darwin = OSX) $(uname -v) will give you the os version see uname manual here
deb package fails while postinst bash script executing
Im making a bash script for nagios custom plugins & configuration i use equivs for its simplicity here'is my control file. in Files: section , i tell files to copy themselves to their right path. Files: check_cpu_loadx /usr/lib/nagios/plugins check_ipmi_sensors /usr/lib/nagios/plugins check_libreoffice_count /usr/lib/nagios/plugins check_ram_per_user /usr/lib/nagios/plugins check_ram_usage2 /usr/lib/nagios/plugins check_ram_usage_percentage /usr/lib/nagios/plugins check_tcptraffic /usr/lib/nagios/plugins nrpe_custom.cfg /etc/nagios in the postinst section , it's a bash script that is used for post-install File: postinst #!/bin/bash -e set -x echo 'configuring nrpe.conf file.' mv /etc/nagios/nrpe.cfg /etc/nagios/nrpe.original.backup mv /etc/nagios/nrpe_custom.cfg /etc/nagios/nrpe.cfg chmod -R +x /usr/lib/nagios/plugins echo 'Installing tcp-ip addon..' FLAG=0 Interfaces=`ifconfig -a | grep -o -e "[a-z][a-z]*[0-9]*[ ]*Link" | perl -pe "s|^([a-z]*[0-9]*)[ ]*Link|\1|"` for Interface in $Interfaces; do INET=`ifconfig $Interface | grep -o -e "inet addr:[^ ]*" | grep -o -e "[^:]*$"` MASK=`ifconfig $Interface | grep -o -e "Mask:[^ ]*" | grep -o -e "[^:]*$"` STATUS="up" #loopback if [ "$Interface" == "lo" ]; then continue fi #if eth is down if [ -z "$INET" ]; then continue fi #if eth ip not starts with 10. or 192. if [[ "$INET" == 10.* ]] then ActiveEth=$Interface; break elif [[ "$INET" == 192.* ]] then ActiveEth=$Interface; break else echo "Ethernet Selection Failed!Configure nrpe.cfg manually.Change tcp_traffic plugin paramethers according to your current ethernet."; FLAG=1 break fi done if [[ "$FLAG" == 0 ]] then echo 'Selected Ethernet :'$ActiveEth sed -i -e "s/eth0/$ActiveEth/g" /etc/nagios/nrpe.cfg fi echo 'nrpe.conf changed.' echo 'Nagios-nrpe-server restarting.' service nagios-nrpe-server restart echo 'IPMI modules are loading.' modprobe ipmi_devintf modprobe ipmi_msghandler echo "IPMI modules are added to startup." #echo "ipmi_si" >> /etc/modules echo "ipmi_devintf" >> /etc/modules echo "ipmi_msghandler" >> /etc/modules the problem here , when i compile it to deb package i got "subprocess installed post-installation script return error exit status 1" then i added set -x for debugging.the problem is for configuring tcp-ip addon , there are some machines that have more then one ethernet card.So i need to choose with the one that has a ip that starts with 10.* or 192.* in the second section , there is a line INET=ifconfig $Interface | grep -o -e "inet addr:[^ ]*" | grep -o -e "[^:]*$" when a ethernet device has no ip , grep returns null and INET variable becomes null , that why the process exit status is 1. after that line , when i enter "$?" , it says 1 so the problem here is , when i run dpkg -i to install that package , bash script quits after it sees that INET becomes null .. any help would be appreciated. Im new to this bash thing.
if you want to make sure that a bash command always succeeds, even if the last program gives a non-null return value, just add a "very last" command that will succeed. something like INET=$(/sbin/ifconfig eth0 | grep -o -e "inet addr:[^ ]*" | grep -o -e "[^:]*$" || true) here we call true (a small program that always succeeds), whenever grep fails (|| means OR and is a way to chain programs depending on the exit state of the previous one) however, your script has a number of flaws: your grep expression "inet addr:" will only give correct results in an english locale; e.g. when running in a german environment (LANG=de) you could get strings like inet Adresse: 192.168.7.10 (sic!) you are unconditionally moving files around; what happens if these files are not there? you are unconditionally moving files in /etc. /etc is the place where the sysadmin adjust the system to their needs; you shall not delete or revert the configuration of the sysadmin. you should rather document how to properly configure the system (so the sysadmins can do it themselves); if you insist in "helping" by automatically configuring the system, you should use something like debconf you assume that a lot of software is installed, and that this software is in your path. you should probably use fully qualified paths to the binaries you are using, e.g. /sbin/ifconfig rather than just ifconfig
How can I get the behavior of GNU's readlink -f on a Mac? [closed]
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers. This question does not appear to be about a specific programming problem, a software algorithm, or software tools primarily used by programmers. If you believe the question would be on-topic on another Stack Exchange site, you can leave a comment to explain where the question may be able to be answered. Closed last year. The community reviewed whether to reopen this question last year and left it closed: Original close reason(s) were not resolved Improve this question On Linux, the readlink utility accepts an option -f that follows additional links. This doesn't seem to work on Mac and possibly BSD based systems. What would the equivalent be? Here's some debug information: $ which readlink; readlink -f /usr/bin/readlink readlink: illegal option -f usage: readlink [-n] [file ...]
MacPorts and Homebrew provide a coreutils package containing greadlink (GNU readlink). Credit to Michael Kallweitt post in mackb.com. brew install coreutils greadlink -f file.txt
readlink -f does two things: It iterates along a sequence of symlinks until it finds an actual file. It returns that file's canonicalized name—i.e., its absolute pathname. If you want to, you can just build a shell script that uses vanilla readlink behavior to achieve the same thing. Here's an example. Obviously you could insert this in your own script where you'd like to call readlink -f #!/bin/sh TARGET_FILE=$1 cd `dirname $TARGET_FILE` TARGET_FILE=`basename $TARGET_FILE` # Iterate down a (possible) chain of symlinks while [ -L "$TARGET_FILE" ] do TARGET_FILE=`readlink $TARGET_FILE` cd `dirname $TARGET_FILE` TARGET_FILE=`basename $TARGET_FILE` done # Compute the canonicalized name by finding the physical path # for the directory we're in and appending the target file. PHYS_DIR=`pwd -P` RESULT=$PHYS_DIR/$TARGET_FILE echo $RESULT Note that this doesn't include any error handling. Of particular importance, it doesn't detect symlink cycles. A simple way to do this would be to count the number of times you go around the loop and fail if you hit an improbably large number, such as 1,000. EDITED to use pwd -P instead of $PWD. Note that this script expects to be called like ./script_name filename, no -f, change $1 to $2 if you want to be able to use with -f filename like GNU readlink.
You may be interested in realpath(3), or Python's os.path.realpath. The two aren't exactly the same; the C library call requires that intermediary path components exist, while the Python version does not. $ pwd /tmp/foo $ ls -l total 16 -rw-r--r-- 1 miles wheel 0 Jul 11 21:08 a lrwxr-xr-x 1 miles wheel 1 Jul 11 20:49 b -> a lrwxr-xr-x 1 miles wheel 1 Jul 11 20:49 c -> b $ python -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' c /private/tmp/foo/a I know you said you'd prefer something more lightweight than another scripting language, but just in case compiling a binary is insufferable, you can use Python and ctypes (available on Mac OS X 10.5) to wrap the library call: #!/usr/bin/python import ctypes, sys libc = ctypes.CDLL('libc.dylib') libc.realpath.restype = ctypes.c_char_p libc.__error.restype = ctypes.POINTER(ctypes.c_int) libc.strerror.restype = ctypes.c_char_p def realpath(path): buffer = ctypes.create_string_buffer(1024) # PATH_MAX if libc.realpath(path, buffer): return buffer.value else: errno = libc.__error().contents.value raise OSError(errno, "%s: %s" % (libc.strerror(errno), buffer.value)) if __name__ == '__main__': print realpath(sys.argv[1]) Ironically, the C version of this script ought to be shorter. :)
A simple one-liner in perl that's sure to work almost everywhere without any external dependencies: perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file Will dereference symlinks. Usage in a script could be like this: readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";} ABSPATH="$(readlinkf ./non-absolute/file)"
You might need both a portable, pure shell implementation, and unit-test coverage, as the number of edge-cases for something like this is non-trivial. See my project on Github for tests and full code. What follows is a synopsis of the implementation: As Keith Smith astutely points out, readlink -f does two things: 1) resolves symlinks recursively, and 2) canonicalizes the result, hence: realpath() { canonicalize_path "$(resolve_symlinks "$1")" } First, the symlink resolver implementation: resolve_symlinks() { local dir_context path path=$(readlink -- "$1") if [ $? -eq 0 ]; then dir_context=$(dirname -- "$1") resolve_symlinks "$(_prepend_path_if_relative "$dir_context" "$path")" else printf '%s\n' "$1" fi } _prepend_path_if_relative() { case "$2" in /* ) printf '%s\n' "$2" ;; * ) printf '%s\n' "$1/$2" ;; esac } Note that this is a slightly simplified version of the full implementation. The full implementation adds a small check for symlink cycles, as well as massages the output a bit. Finally, the function for canonicalizing a path: canonicalize_path() { if [ -d "$1" ]; then _canonicalize_dir_path "$1" else _canonicalize_file_path "$1" fi } _canonicalize_dir_path() { (cd "$1" 2>/dev/null && pwd -P) } _canonicalize_file_path() { local dir file dir=$(dirname -- "$1") file=$(basename -- "$1") (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file") } That's it, more or less. Simple enough to paste into your script, but tricky enough that you'd be crazy to rely on any code that doesn't have unit tests for your use cases.
Install homebrew Run "brew install coreutils" Run "greadlink -f path" greadlink is the gnu readlink that implements -f. You can use macports or others as well, I prefer homebrew.
I made a script called realpath personally which looks a little something like: #!/usr/bin/env python import os, sys print(os.path.realpath(sys.argv[1]))
What about this? function readlink() { DIR="${1%/*}" (cd "$DIR" && echo "$(pwd -P)") }
A lazy way that works for me, $ brew install coreutils $ ln -s /usr/local/bin/greadlink /usr/local/bin/readlink $ which readlink /usr/local/bin/readlink /usr/bin/readlink
Implementation Install brew Follow the instructions at https://brew.sh/ Install the coreutils package brew install coreutils Create an Alias or Symlink 3a. Create an an alias (per user) You can place your alias in ~/.bashrc, ~/.bash_profile, or wherever you are used to keeping your bash aliases. I personally keep mine in ~/.bashrc alias readlink=greadlink 3b. Create a symbolic link (system wide) ln -s /usr/local/bin/greadlink /usr/local/bin/readlink (credit: Izana) This will create a symbolic link in /usr/local/bin while keeping the original readlink binary in tact. It works because the search for readlink will return 2 results. But the second in /usr/local/bin will take precedence. e.g. which readlink To undo this change simply unlink /usr/local/bin/readlink Additional Tools You can create similar aliases or symlinks for other coreutils such as gmv, gdu, gdf, and so on. But beware that the GNU behavior on a mac machine may be confusing to others used to working with native coreutils, or may behave in unexpected ways on your mac system. Explanation coreutils is a brew package that installs GNU/Linux core utilities which correspond to the Mac OSX implementation of them so that you can use those You may find programs or utilties on your mac osx system which seem similar to Linux coreutils ("Core Utilities") yet they differ in some ways (such as having different flags). This is because the Mac OSX implementation of these tools are different. To get the original GNU/Linux-like behavior you can install the coreutils package via the brew package management system. This will install corresponding core utilities, prefixed by g. E.g. for readlink, you will find a corresponding greadlink program. In order to make readlink perform like the GNU readlink (greadlink) implementation, you can create a simple alias or symbolic link after you install coreutils.
FreeBSD and OSX have a version of statderived from NetBSD. You can adjust the output with format switches (see the manual pages at the links above). % cd /service % ls -tal drwxr-xr-x 22 root wheel 27 Aug 25 10:41 .. drwx------ 3 root wheel 8 Jun 30 13:59 .s6-svscan drwxr-xr-x 3 root wheel 5 Jun 30 13:34 . lrwxr-xr-x 1 root wheel 30 Dec 13 2013 clockspeed-adjust -> /var/service/clockspeed-adjust lrwxr-xr-x 1 root wheel 29 Dec 13 2013 clockspeed-speed -> /var/service/clockspeed-speed % stat -f%R clockspeed-adjust /var/service/clockspeed-adjust % stat -f%Y clockspeed-adjust /var/service/clockspeed-adjust Some OS X versions of stat may lack the -f%R option for formats. In this case -stat -f%Y may suffice. The -f%Y option will show the target of a symlink, whereas -f%R shows the absolute pathname corresponding to the file. EDIT: If you're able to use Perl (Darwin/OS X comes installed with recent verions of perl) then: perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt will work.
The easiest way to solve this problem and enable the functionality of readlink on Mac w/ Homebrew installed or FreeBSD is to install 'coreutils' package. May also be necessary on certain Linux distributions and other POSIX OS. For example, in FreeBSD 11, I installed by invoking: # pkg install coreutils On MacOS with Homebrew, the command would be: $ brew install coreutils Not really sure why the other answers are so complicated, that's all there is to it. The files aren't in a different place, they're just not installed yet.
Here is a portable shell function that should work in ANY Bourne comparable shell. It will resolve the relative path punctuation ".. or ." and dereference symbolic links. If for some reason you do not have a realpath(1) command, or readlink(1) this can be aliased. which realpath || alias realpath='real_path' Enjoy: real_path () { OIFS=$IFS IFS='/' for I in $1 do # Resolve relative path punctuation. if [ "$I" = "." ] || [ -z "$I" ] then continue elif [ "$I" = ".." ] then FOO="${FOO%%/${FOO##*/}}" continue else FOO="${FOO}/${I}" fi ## Resolve symbolic links if [ -h "$FOO" ] then IFS=$OIFS set `ls -l "$FOO"` while shift ; do if [ "$1" = "->" ] then FOO=$2 shift $# break fi done IFS='/' fi done IFS=$OIFS echo "$FOO" } also, just in case anybody is interested here is how to implement basename and dirname in 100% pure shell code: ## http://www.opengroup.org/onlinepubs/000095399/functions/dirname.html # the dir name excludes the least portion behind the last slash. dir_name () { echo "${1%/*}" } ## http://www.opengroup.org/onlinepubs/000095399/functions/basename.html # the base name excludes the greatest portion in front of the last slash. base_name () { echo "${1##*/}" } You can find updated version of this shell code at my google site: http://sites.google.com/site/jdisnard/realpath EDIT: This code is licensed under the terms of the 2-clause (freeBSD style) license. A copy of the license may be found by following the above hyperlink to my site.
Begin Update This is such a frequent problem that we have put together a Bash 4 library for free use (MIT License) called realpath-lib. This is designed to emulate readlink -f by default and includes two test suites to verify (1) that it works for a given unix system and (2) against readlink -f if installed (but this is not required). Additionally, it can be used to investigate, identify and unwind deep, broken symlinks and circular references, so it can be a useful tool for diagnosing deeply-nested physical or symbolic directory and file problems. It can be found at github.com or bitbucket.org. End Update Another very compact and efficient solution that does not rely on anything but Bash is: function get_realpath() { [[ ! -f "$1" ]] && return 1 # failure : file does not exist. [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks. echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result. return 0 # success } This also includes an environment setting no_symlinks that provides the ability to resolve symlinks to the physical system. As long as no_symlinks is set to something, ie no_symlinks='on' then symlinks will be resolved to the physical system. Otherwise they will be applied (the default setting). This should work on any system that provides Bash, and will return a Bash compatible exit code for testing purposes.
There are already a lot of answers, but none worked for me... So this is what I'm using now. readlink_f() { local target="$1" [ -f "$target" ] || return 1 #no nofile while [ -L "$target" ]; do target="$(readlink "$target")" done echo "$(cd "$(dirname "$target")"; pwd -P)/$target" }
Since my work is used by people with non-BSD Linux as well as macOS, I've opted for using these aliases in our build scripts (sed included since it has similar issues): ## # If you're running macOS, use homebrew to install greadlink/gsed first: # brew install coreutils # # Example use: # # Gets the directory of the currently running script # dotfilesDir=$(dirname "$(globalReadlink -fm "$0")") # alias al='pico ${dotfilesDir}/aliases.local' ## function globalReadlink () { # Use greadlink if on macOS; otherwise use normal readlink if [[ $OSTYPE == darwin* ]]; then greadlink "$#" else readlink "$#" fi } function globalSed () { # Use gsed if on macOS; otherwise use normal sed if [[ $OSTYPE == darwin* ]]; then gsed "$#" else sed "$#" fi } Optional check you could add to automatically install homebrew + coreutils dependencies: if [[ "$OSTYPE" == "darwin"* ]]; then # Install brew if needed if [ -z "$(which brew)" ]; then /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"; fi # Check for coreutils if [ -z "$(brew ls coreutils)" ]; then brew install coreutils fi fi I suppose to be truly "global" it needs to check others...but that probably comes close to the 80/20 mark.
POSIX compliant readlink -f implementation for POSIX shell scripts https://github.com/ko1nksm/readlinkf This is POSIX compliant (no bashism). It uses neither readlink nor realpath. I have verified that it is exactly the same by comparing with GNU readlink -f (see test results). It has error handling and good performance. You can safely replace from readlink -f. The license is CC0, so you can use it for any project. This code is adopted in the bats-core project. # POSIX compliant version readlinkf_posix() { [ "${1:-}" ] || return 1 max_symlinks=40 CDPATH='' # to avoid changing to an unexpected directory target=$1 [ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes [ -d "${target:-/}" ] && target="$target/" cd -P . 2>/dev/null || return 1 while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do if [ ! "$target" = "${target%/*}" ]; then case $target in /*) cd -P "${target%/*}/" 2>/dev/null || break ;; *) cd -P "./${target%/*}" 2>/dev/null || break ;; esac target=${target##*/} fi if [ ! -L "$target" ]; then target="${PWD%/}${target:+/}${target}" printf '%s\n' "${target:-/}" return 0 fi # `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n", # <file mode>, <number of links>, <owner name>, <group name>, # <size>, <date and time>, <pathname of link>, <contents of link> # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html link=$(ls -dl -- "$target" 2>/dev/null) || break target=${link#*" $target -> "} done return 1 } Please refer to the latest code. It may some fixed.
Better late than never, I suppose. I was motivated to develop this specifically because my Fedora scripts weren't working on the Mac. The problem is dependencies and Bash. Macs don't have them, or if they do, they are often somewhere else (another path). Dependency path manipulation in a cross-platform Bash script is a headache at best and a security risk at worst - so it's best to avoid their use, if possible. The function get_realpath() below is simple, Bash-centric, and no dependencies are required. I uses only the Bash builtins echo and cd. It is also fairly secure, as everything gets tested at each stage of the way and it returns error conditions. If you don't want to follow symlinks, then put set -P at the front of the script, but otherwise cd should resolve the symlinks by default. It's been tested with file arguments that are {absolute | relative | symlink | local} and it returns the absolute path to the file. So far we've not had any problems with it. function get_realpath() { if [[ -f "$1" ]] then # file *must* exist if cd "$(echo "${1%/*}")" &>/dev/null then # file *may* not be local # exception is ./file.ext # try 'cd .; cd -;' *works!* local tmppwd="$PWD" cd - &>/dev/null else # file *must* be local local tmppwd="$PWD" fi else # file *cannot* exist return 1 # failure fi # reassemble realpath echo "$tmppwd"/"${1##*/}" return 0 # success } You can combine this with other functions get_dirname, get_filename, get_stemname and validate_path. These can be found at our GitHub repository as realpath-lib (full disclosure - this is our product but we offer it free to the community without any restrictions). It also could serve as a instructional tool - it's well documented. We've tried our best to apply so-called 'modern Bash' practices, but Bash is a big subject and I'm certain there will always be room for improvement. It requires Bash 4+ but could be made to work with older versions if they are still around.
echo $(cd $(dirname file1) ; pwd -P)
I wrote a realpath utility for OS X which can provide the same results as readlink -f. Here is an example: (jalcazar#mac tmp)$ ls -l a lrwxrwxrwx 1 jalcazar jalcazar 11 8月 25 19:29 a -> /etc/passwd (jalcazar#mac tmp)$ realpath a /etc/passwd If you are using MacPorts, you can install it with the following command: sudo port selfupdate && sudo port install realpath.
Truely platform-indpendent would be also this R-onliner readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";} To actually mimic readlink -f <path>, $2 instead of $1 would need to be used.
I have simply pasted the following to the top of my bash scripts: #!/usr/bin/env bash -e declare script=$(basename "$0") declare dirname=$(dirname "$0") declare scriptDir if [[ $(uname) == 'Linux' ]];then # use readlink -f scriptDir=$(readlink -f "$dirname") else # can't use readlink -f, do a pwd -P in the script directory and then switch back if [[ "$dirname" = '.' ]];then # don't change directory, we are already inside scriptDir=$(pwd -P) else # switch to the directory and then switch back pwd=$(pwd) cd "$dirname" scriptDir=$(pwd -P) cd "$pwd" fi fi And removed all instances of readlink -f. $scriptDir and $script then will be available for the rest of the script. While this does not follow all symlinks, it works on all systems and appears to be good enough for most use cases, it switches the directory into the containing folder, and then it does a pwd -P to get the real path of that directory, and then finally switch back to the original.
Perl has a readlink function (e.g. How do I copy symbolic links in Perl?). This works across most platforms, including OS X: perl -e "print readlink '/path/to/link'" For example: $ mkdir -p a/b/c $ ln -s a/b/c x $ perl -e "print readlink 'x'" a/b/c
The answer from #Keith Smith gives an infinite loop. Here is my answer, which i use only on SunOS (SunOS miss so much POSIX and GNU commands). It's a script file you have to put in one of your $PATH directories: #!/bin/sh ! (($#)) && echo -e "ERROR: readlink <link to analyze>" 1>&2 && exit 99 link="$1" while [ -L "$link" ]; do lastLink="$link" link=$(/bin/ls -ldq "$link") link="${link##* -> }" link=$(realpath "$link") [ "$link" == "$lastlink" ] && echo -e "ERROR: link loop detected on $link" 1>&2 && break done echo "$link"
This is what I use: stat -f %N $your_path
The paths to readlink are different between my system and yours. Please try specifying the full path: /sw/sbin/readlink -f