Find precompiled or compile Tcl/Tk Frameworks for macOS - compilation

I am a beginner with Tcl/Tk, but I am already in need to deploy an .app with Tcl/Tk frameworks in it (because the .app needs to rely on its own Tcl/Tk version and not on macOS standard and old Tcl/Tk). Can somebody point me to where I can find precompiled Frameworks or to a step-by-step guide on how to compile them on macOS?
Half a day of search has not been very fruitful in this regard. I guess my need is not at all very special, as I see many .apps shipping their own Tcl/Tk Frameworks. (PS: I need to access these Frameworks from Perl, but this shouldn't make any difference.)

Here is my build script. You will need to make modifications to suit
your needs.
I make modifications to init.tcl so that Tcl will search in the proper locations
and will not look first in the MacOS standard location. This part of the
script will break starting with Tcl/Tk 8.7. It also has a path that is
very specific to my installation (the 'darwin 64 tcl lib' part).
It won't hurt anything to leave it in, or you can modify the path for your needs.
I make the application relocatable so that it can be run from any location.
This can be a security issue. It is recommended that additional processing be
done after installation to set the library paths to a static location.
I personally recommend Tcl/Tk 8.6.8 at this time, but it has to be built with
an earlier XCode version (I use XCode Command Line Tools 9.2 on Sierra).
There are still various bug fixes for MacOS Mojave being worked on.
Other people may recommend otherwise.
Variables:
macosxminver : The earliest version of MacOS that Tcl/Tk will be compiled for.
INSTLOC : The installation location. Point this somewhere other than your target
directory and copy the files to your target directory afterwards.
Note that the INSTLOC directory is completely removed by the script.
sver : short Tcl version. The only place this is used is in the SRCDIR definition.
You may not need this depending on your SRCDIR directory structure.
ver : Tcl version number.
mver : Tcl major version number.
SRCDIR : Where the Tcl/Tk source trees are located. For the purposes of this
script, I have a directory with the following structure:
directory structure:
tcl868/
tcl8.6.8/
tk8.6.8/
tclbuild.sh:
#!/bin/bash
macosxminver=10.9
sver=868cp
ver=8.6.8
mver=8.6
tclmver=$mver
tkmver=$mver
SRCDIR=$HOME/t/tcl${sver}
INSTLOC=$HOME/t/localB
if [[ $1 != "" ]]; then
INSTLOC=$1
fi
if [[ -d $INSTLOC ]]; then
rm -rf $INSTLOC
fi
mkdir $INSTLOC
cd $SRCDIR
test -d build && rm -rf build
cd $SRCDIR
cd tcl${ver}
if [[ $? -eq 0 ]]; then
f=library/init.tcl
if [[ ! -f $f-orig ]]; then
cp -pf $f $f-orig
fi
cp -pf $f-orig $f
ed $f << _HERE_
/issafe/
i
foreach Dir [list \$::tcl_library [file dirname \$::tcl_library]] {
if { [string match *Tcl.framework* \$Dir] } {
regsub Tcl.framework \$Dir Tk.framework Dir
if {\$Dir ni \$::auto_path} {
lappend ::auto_path \$Dir
}
}
}
# This needs to be at the end
# The real wish executable is in an odd place.
# Find the tcl directory in the path.
set Dir [file dirname [info nameofexecutable]]
if { [string match *MacOS* \$Dir] } {
regsub {MacOS.*} \$Dir {MacOS} Dir
set Dir [file join \$Dir darwin 64 tcl lib]
lappend ::auto_path \$Dir
} else {
set Dir [file join [file dirname [file dirname \\
[info nameofexecutable]]] lib]
}
.
?catch
?set Dir
.,.+4 s/^/#/
/catch
.+1,.+5 s/^/#/
w
q
_HERE_
make -C macosx \
PREFIX="" \
CFLAGS_OPTIMIZE="-O2 -mmacosx-version-min=${macosxminver}" \
INSTALL_ROOT=$INSTLOC install
cd $SRCDIR
chmod u+w $INSTLOC/bin/tclsh${tclmver}
install_name_tool -change \
"/Library/Frameworks/Tcl.framework/Versions/${tclmver}/Tcl" \
#executable_path/../Library/Frameworks/Tcl.framework/Versions/${tclmver}/Tcl \
$INSTLOC/bin/tclsh${tclmver}
fi
cd $SRCDIR
cd tk${ver}
if [[ $? -eq 0 ]]; then
make -C macosx \
PREFIX="" \
CFLAGS_OPTIMIZE="-O2 -mmacosx-version-min=${macosxminver}" \
INSTALL_ROOT=$INSTLOC install
cd $SRCDIR
chmod u+w $INSTLOC/Library/Frameworks/Tk.framework/Versions/${tkmver}/Resources/Wish.app/Contents/MacOS/Wish
install_name_tool -change \
"/Library/Frameworks/Tk.framework/Versions/${tkmver}/Tk" \
#executable_path/../../../../Tk \
$INSTLOC/Library/Frameworks/Tk.framework/Versions/${tkmver}/Resources/Wish.app/Contents/MacOS/Wish
install_name_tool -change \
"/Library/Frameworks/Tcl.framework/Versions/${tclmver}/Tcl" \
#executable_path/../../../../../../../Tcl.framework/Versions/${tclmver}/Tcl \
$INSTLOC/Library/Frameworks/Tk.framework/Versions/${tkmver}/Resources/Wish.app/Contents/MacOS/Wish
fi
cd $SRCDIR
find $INSTLOC -type f -print0 | xargs -0 chmod u+w
exit 0

The proposed script fails for me on Mojave, after compiling for several minutes, with the following error:
ERROR: version conflict for package "Tcl": have 8.5.9, need 8.6-
If running this script from 'make html', set the NATIVE_TCLSH environment
variable to point to an installed tclsh8.6 (or the equivalent tclsh86.exe
on Windows).
make[3]: *** [html-tcl] Error 1
make[2]: *** [install-strip] Error 2
make[1]: *** [install-tcl] Error 2
make: *** [install-deploy] Error 2
tclbuild.sh: line 74: cd: /Users/xl/t/tcl869cp: No such file or directory
tclbuild.sh: line 83: cd: /Users/xl/t/tcl869cp: No such file or directory
tclbuild.sh: line 84: cd: tk8.6.9: No such file or directory
tclbuild.sh: line 103: cd: /Users/xl/t/tcl869cp: No such file or directory
I have the latest Tcl/Tk (8.6.9) resources and the same folder structure as indicated above.

Related

windows: copy with hardlinks for backup

in case I accidentally modify/delete important documents, my linux PC makes daily backups with a script that gets executed by cron and contains the following line.
rsync --checksum --recursive ${source} ${dest}/$i --link-dest=${dest}/$((i-1))
(${source} ist the path of the documents folder, ${dest}/n is the path of the n-th backup.)
Using the --link-dest option has the great advantage, that if you backup a 3 GB Folder, change on small file and backup again, both backups combined need 3 GB disk space, instead of 6 GB if I would run rsync without the --link-dest option.
I'm struggling to write a similar script for windows: I could just use the cp -r powershell command (or the xcopy cmd command) but this command does not have an option that is similar to rsync's --link-dest option. Using the linux subsystem for windows for the rsync command works, but scripts in the cron.daily folder inside the linux subsystem for windows do net get executed daily.
TLDR: What is the windows equivalent of rsync -r pathA pathB --link-dest pathC
PS: In case anyone wants the linux version of the script for his own backups, here it is:
#!/bin/bash
source=/home/username/documents
dest=/myBackup
if [ "$1" == "--install" ] ; then
echo "installing..."
cp $0 /etc/cron.daily/myBackupScript
mkdir $dest
echo "installed"
exit 0
fi
for i in {0..9999}; do
if [ ! -e ${dest}/$i ]; then
echo "Copying to " ${dest}/$i
if [ -d ${dest}/$((i-1)) ]; then
rsync --checksum --recursive ${source} ${dest}/$i --link-dest=${dest}/$((i-1))
else
rsync --checksum --recursive ${source} ${dest}/$i
fi
DATE=`date +%Y-%m-%d__%H:%M:%S`
touch ${dest}/$i/$DATE
exit 0
fi
done
echo "unable to do backup"
exit 4
The current rsync version (3.2.2) from the MSYS2 collection for Windows (install: pacman -S rsync), supports the --link-dest hardlink re-use option correctly on NTFS. It also supports NTFS unicode filenames now.
Absolute paths have to be given in MSYS / Cygwin convention - e.g. /C/path/to/source/.
Note: So far (2021-02) the MSYS2 rsync cannot create / replicate symbolic links in the destination using any of the symlink options. It would create content copies instead. Yet it can detect and exclude symlinks in the source.

dart2js throwing no such file or directory error

Quite simply, I have a link to dart2js in my /usr/local/bin/ which throws repeated erros when run.
I ran
sudo ln -s /Users/macbook/Development/dart/dart-sdk/bin/dart2js /usr/local/bin/dart2js
When running dart2js from terminal, I'm presented with
...
/usr/local/bin/dart2js: line 9: 1=/Users/macbook/Development/dart/dart-sdk/bin/dart2js: No such file or directory
/usr/local/bin/dart2js: line 9: 1=/Users/macbook/Development/dart/dart-sdk/bin/dart2js: No such file or directory
/usr/local/bin/dart2js: line 9: 1=/Users/macbook/Development/dart/dart-sdk/bin/dart2js: No such file or directory
/usr/local/bin/dart2js: line 9: 1=/Users/macbook/Development/dart/dart-sdk/bin/dart2js: No such file or directory
/usr/local/bin/dart2js: line 9: 1=/Users/macbook/Development/dart/dart-sdk/bin/dart2js: No such file or directory
... etc
I imagine I'm simply using links incorrectly, but I'm not knowledgable enough to know why.
I run into this a while ago and posted this issue Can't run dart command line script using a symlink
I also added a workaround which may help in your situation as well - you probably need to adapt it a bit but it should get you started.
A workaround I use now is to create a bash script named 'testscript' in the same directory as testscript.dart and link to this script instead
I lookup the current directory of the bash script and start the dart script
(Getting the source directory of a Bash script from within).
#!/bin/bash
ME="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")"
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
${DIR}/${ME}.dart ${BASH_SOURCE[0]}
Instead of getting the command's (symlinks) name with io.Platform.script I have to pass it as an argument to have it available inside the dart script (I use the name of the symlink used to start the script like an argument inside the dart script)
This way I have to distinguish if the script was started directly and use io.Platform.script and args[0] otherwise.
I think this should all be easier.

Unexpected behavior from shell script's ln -sfn -v

I have the following:
if [ $kernel == 'Darwin' ]; then
$HOME="/Users/$user"
elif [ $kernel == 'Linux' ]; then
$HOME="/home/$user"
fi
# Let the script know what the dotfiles dir is
dotfiles_dir="$HOME/dotfiles"
# Making symlinks to shell files, add yours as you need
echo 'Making symlinks to shell files'
ln -sfn -v $dotfiles_dir/shells/zsh $HOME/.zsh
ln -sfn -v $dotfiles_dir/shells/zsh/zshrc $HOME/.zshrc
ln -sfn -v $dotfiles_dir/shells/bash $HOME/.bash
ln -sfn -v $dotfiles_dir/shells/bash/bash_profile $HOME/.bash_profile
ln -sfn -v $dotfiles_dir/shells/bash/bashrc $HOME/.bashrc
ln -sfn -v $dotfiles_dir/shells/profile $HOME/.profile
echo "Done at [$time]...\n"
However, this strangely outputs:
[Other output here...]
Making symlinks shell files
/Users/eduan/.zsh -> /Users/eduan/dotfiles/shells/zsh
/Users/eduan/.zshrc -> /Users/eduan/dotfiles/shells/zsh/zshrc
/Users/eduan/.bash -> /Users/eduan/dotfiles/shells/bash
./bashrc -> /Users/eduan/dotfiles/shells/bash/bashrc
/Users/eduan/.profile -> /Users/eduan/dotfiles/shells/profile
Done at [08:15]...
[Other output here...]
Can anybody tell me why I get this unexpected output? The rest of my symlinks are generated correctly, however the Bash symlinks are messed up for some reason.
BTW, this is in a big script that generates symlinks. This is only part of it, and I put the relevant parts, so that you don't get confused. :)
EDIT:
Here's the link to the latest version of the script: https://github.com/Greduan/dotfiles/blob/master/scripts/make_symlinks.sh
The darwin version of the symlink commands is missing the destination for bashrc, so going to the current directory.
I'm not sure why there are two versions of that bit of the script?
Also you only create the backup directory if it already exists, which can't be right?
This line is wrong, isn't it. Missing destination directory.
ln -sfn -v $dotfiles_dir/shells/bash/bashrc

Bash script to safely create symlinks?

I'm trying to store all my profile configuration files (~/.xxx) in git. I'm pretty horrible at bash scripting but I imagine this will be pretty straight forward for you scripting gurus.
Basically, I'd like a script that will create symbolic links in my home directory to files in my repo. Twist is, I'd like it warn and prompt for overwrite if the symlink will be overwriting an actual file. It should also prompt if a sym link is going to be overwritten, but the target path is different.
I don't mind manually editing the script for each link I want to create. I'm more concerned with being able to quickly deploy new config scripts by running this script stored in my repo.
Any ideas?
The ln command is already conservative about erasing, so maybe the KISS approach is good enough for you:
ln -s git-stuff/home/.[!.]* .
If a file or link already exists, you'll get an error message and this link will be skipped.
If you want the files to have a different name in your repository, pass the -n option to ln so that it doesn't accidentally create a symlink in an existing subdirectory of that name:
ln -sn git-stuff/home/profile .profile
...
If you also want to have links in subdirectories of your home directory, cp -as reproduces the directory structure but creates symbolic links for regular files. With the -i option, it prompts if a target already exists.
cp -i -as git-stuff/home/.[!.]* .
(My answer assumes GNU ln and GNU cp, such as you'd find on Linux (and Cygwin) but usually not on other unices.)
The following has race conditions, but it is probably as safe as you can get without filesystem transactions:
# create a symlink at $dest pointing to $source
# not well tested
set -e # abort on errors
if [[ ( -h $dest && $(readlink -n "$dest") != $source ) || -f $dest || -d $dest ]]
then
read -p "Overwrite $dest? " answer
else
answer=y
fi
[[ $answer == y ]] && ln -s -n -f -v -- "$source" "$dest"

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

Resources