Found on web; dedicated to mk and deseven (if this can be useful):
Code: Select all
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
-- simple call
set x1 to my doShellScript:"echo 'Hello world'"
-- calling tool directly instead of via /bin/sh
set x2 to my runTool:"/usr/bin/tr" withArgs:{"a", "o"} stdInput:"bad cat" envDict:(missing value) useLocale:false useReturns:false stripLast:true resultType:(text)
-- an old Nigel Garvey example revisited out of season, showing use of locale
-- set x3 to my doShellScriptLocalized:("echo 'x-93yß<⌘wß⌘-r7ßßq' | sed 'y?<379-x⌘qß?Np!paHe" & character id 127863 & "Y ?'")
-- do comparison
set thePath to POSIX path of (choose file)
set x4 to do shell script "cat " & quoted form of thePath
set x5 to my doShellScript:("cat " & quoted form of thePath)
return x4 = x5
-- This matches plain 'do shell script'
on doShellScript:theCommand
return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:false useReturns:true stripLast:true resultType:(text)
end doShellScript:
-- This matches 'do shell script without altering line endings'
on doShellScriptNoAltering:theCommand
return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:false useReturns:false stripLast:false resultType:(text)
end doShellScriptNoAltering:
-- This matches 'do shell script without altering line endings' but still strips the trailing linefeed
on doShellScriptWithLFs:theCommand
return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:false useReturns:false stripLast:true resultType:(text)
end doShellScriptWithLFs:
--- This lets you configure line ending behavior and the result type
-- resultType: string = matches osax; data = like osax "as data"; "NSString" = NSString if UTF-8, else unmodified NSData; "NSData" = unmodified NSData; "Base-64" = unmodified data as Base-64 string
on doShellScript:theCommand useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:false useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
end doShellScript:useReturns:stripLast:resultType:
-- This lets you configure most things
-- resultType: string = matches osax; data = like osax "as data"; "NSString" = NSString if UTF-8, else unmodified NSData; "NSData" = unmodified NSData; "Base-64" = unmodified data as Base-64 string
-- useReturns and stripLast are booleans; envDict is a record/NSDictionary or missing value; stdInput is a string/NSString or missing value
-- localeValue: false or 0 = ignore; true or 1 = set LANG, LC_CTYPE, and LC_COLLATE to current locale; 3 = sets LC_ALL to current locale
on doShellScript:theCommand stdInput:theInput envDict:envDict useLocale:localeValue useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:theInput envDict:envDict useLocale:localeValue useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
end doShellScript:stdInput:envDict:useLocale:useReturns:stripLast:resultType:
-- This matches plain 'do shell script' but sets LANG, LC_CTYPE, and LC_COLLATE to current locale
on doShellScriptLocalized:theCommand
return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:true useReturns:true stripLast:true resultType:(text)
end doShellScriptLocalized:
-- This matches 'do shell script without altering line endings' but sets LANG, LC_CTYPE, and LC_COLLATE to current locale
on doShellScriptNoAlteringLocalized:theCommand
return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:true useReturns:false stripLast:false resultType:(text)
end doShellScriptNoAlteringLocalized:
-- This matches 'do shell script without altering line endings' but still strips the trailing linefeed and uses sets LANG, LC_CTYPE, and LC_COLLATE to current locale
on doShellScriptWithLFsLocalized:theCommand
return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:true useReturns:false stripLast:true resultType:(text)
end doShellScriptWithLFsLocalized:
-- This lets you configure line ending behavior, and whether to return an NSString or text; sets LANG, LC_CTYPE, and LC_COLLATE to current locale
-- resultType: 1 = string, 2 = NSString, 3 = NSData, 4 = Base-64 string; useReturns and stripLast are booleans
on doShellScriptLocalized:theCommand useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:true useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
end doShellScriptLocalized:useReturns:stripLast:resultType:
-- This is the "master" handler called by the above. You can also call it directly, to address tools directly or to use a different shell. In this case you pass the arguments as a list, and you must use the path to the tool.
-- For example: runTool:"/usr/bin/tr" withArgs:{"a", "o"} stdInput:"bad cat" envDict:(missing value) useLocale:false useReturns:false stripLast:true resultType:text
-- resultType: string = matches osax; data = like osax "as data"; "NSString" = NSString if UTF-8, else unmodified NSData; "NSData" = unmodified NSData; "Base-64" = unmodified data as Base-64 string
-- useReturns and stripLast are booleans; envDict is a record/NSDictionary or missing value; stdInput is a string/NSString or missing value
-- localeValue: false or 0 = ignore; true or 1 = set LANG, LC_CTYPE, and LC_COLLATE to current locale; 3 = sets LC_ALL to current locale
on runTool:thePath withArgs:theArgList stdInput:theInput envDict:envDict useLocale:localeValue useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
set envMutDict to current application's NSMutableDictionary's dictionary()
if localeValue as integer > 0 then
set localeName to current application's NSLocale's currentLocale()'s localeIdentifier()
if localeValue as integer = 1 then
envMutDict's addEntriesFromDictionary:{LANG:localeName, LC_CTYPE:localeName, LC_COLLATE:localeName}
else
envMutDict's setObject:localeName forKey:"LC_ALL"
end if
end if
if envDict is not missing value then envMutDict's addEntriesFromDictionary:envDict
-- create a pipe for standard output, then get a file handle for reading from it
set outPipe to current application's NSPipe's pipe()
set outFileHandle to outPipe's fileHandleForReading()
-- create a pipe for standard error
set errPipe to current application's NSPipe's pipe()
-- make task, set its properties
set theTask to current application's NSTask's new()
set outData to current application's NSMutableData's |data|()
theTask's setLaunchPath:thePath
theTask's setArguments:theArgList
theTask's setStandardOutput:outPipe
theTask's setStandardError:errPipe
if envMutDict's |count|() > 0 then theTask's setEnvironment:envMutDict
if theInput is missing value then
theTask's |launch|()
else
-- convert the input to data
set theInput to current application's NSString's stringWithString:theInput
set theInputData to theInput's dataUsingEncoding:(current application's NSUTF8StringEncoding)
-- create a pipe for standard input, then get a file handle for writing to it
set inputPipe to current application's NSPipe's pipe()
set inputFileHandle to inputPipe's fileHandleForWriting()
-- connect pipe to task and launch it
theTask's setStandardInput:inputPipe
theTask's |launch|()
-- write to standard input
inputFileHandle's writeData:theInputData
inputFileHandle's closeFile()
end if
-- collect standard output while it's running
repeat while theTask's isRunning() as boolean
set newData to outFileHandle's availableData()
if newData is not missing value then outData's appendData:newData
end repeat
-- check if all went OK
set taskStatus to theTask's |terminationStatus|() as integer
if taskStatus = 0 then -- all OK
set newData to outFileHandle's readDataToEndOfFile()
outFileHandle's closeFile()
if newData is not missing value then outData's appendData:newData
if outData's |length|() < 1 then
-- some tools output to standard error regardless, so check there instead
set outData to errPipe's fileHandleForReading()'s readDataToEndOfFile()
errPipe's fileHandleForReading()'s closeFile()
end if
if resultType = "NSData" then return outData's |copy|()
if resultType = data then -- convert to AS data
set theCode to current application's NSHFSTypeCodeFromFileType("'rdat'")
return (current application's NSAppleEventDescriptor's descriptorWithDescriptorType:theCode |data|:outData) as data
end if
if resultType = "Base-64" then return (outData's base64EncodedStringWithOptions:0) as text
set theString to current application's NSString's alloc()'s initWithData:outData encoding:(current application's NSUTF8StringEncoding)
if theString is missing value then -- not valid UTF-8
if resultType = text then
-- Output as MacRoman, as 'do shell script' does. Awful, but compatible...
set theString to current application's NSString's alloc()'s initWithData:outData encoding:(current application's NSMacOSRomanStringEncoding)
-- if 'altering line endings' is true, 'do shell script' also changes CRLF to LF, so to match...
if useReturnsFlag then set theString to theString's stringByReplacingOccurrencesOfString:(return & linefeed) withString:return
else -- resultType "NSString", fall back to raw data
return outData's |copy|()
end if
else if stripLastFlag then -- only if it's UTF-8
if (theString's hasSuffix:linefeed) as boolean then set theString to theString's substringToIndex:((theString's |length|()) - 1)
end if
if useReturnsFlag then set theString to theString's stringByReplacingOccurrencesOfString:linefeed withString:return
if resultType is "NSString" then return theString
return theString as text
else -- there was an error, so get the data from standardError
set errData to errPipe's fileHandleForReading()'s readDataToEndOfFile()
errPipe's fileHandleForReading()'s closeFile()
set theString to current application's NSString's alloc()'s initWithData:errData encoding:(current application's NSUTF8StringEncoding)
if theString is missing value then
-- match 'do shell script' fallback
set theString to current application's NSString's alloc()'s initWithData:outData encoding:(current application's NSUTF8StringEncoding)
end if
if theString is missing value then
error number taskStatus
else
error (theString as string) number taskStatus
end if
end if
end runTool:withArgs:stdInput:envDict:useLocale:useReturns:stripLast:resultType: