# Merging two variables line by line

Hi there, I was wondering if someone could help me with this. I have two variables each storing two separate lists.

the stored info in variable 1 is:

1,79,347,3
2,82,390,3
3,82,433,3
4,79,476,3
5,82,519,3
6,82,562,3
7,82,605,3
8,82,648,3

and variable 2 has:

74,211,1
74,227,1
74,243,1
74,259,1
74,275,1
74,291,1
74,307,1
74,323,1

The number of lines will always be the same, so in this case, there are 8 lines in each list. I was wondering how I would combine each line from variable 1 and variable 2 to make a new 3rd variable that would look like this

1,79,347,3,74,211,1
2,82,390,3,74,227,1
3,82,433,3,74,243,1
4,79,476,3,74,259,1
5,82,519,3,74,275,1
6,82,562,3,74,291,1
7,82,605,3,74,307,1
8,82,648,3,74,323,1

Thanks for the help!

Hi Billy,

A similar question was posed and answered here:

Take a look through that thread and see if you can't make one of the three possible solutions there work for you. If you run into any problems getting one of them to work, feel free to post again with further questions.

1 Like

beautiful, thank you, thats exactly it.

1 Like

and in JS, perhaps:

Columns from two variables.kmmacros (19.5 KB)

``````(() => {
'use strict';

const main = () => {
const
kme = Application('Keyboard Maestro Engine'),
strTwoCols = unlines(
zipWith(
(x, y) => x + ',' + y,
lines(kme.getvariable('firstCol')),
lines(kme.getvariable('secondCol'))
)
);

return (
kme.setvariable('twoCols', {
to: strTwoCols
}),
strTwoCols
);
};

// GENERIC FUNCTIONS --------------------------------------

// lines :: String -> [String]
const lines = s => s.split(/[\r\n]/);

// unlines :: [String] -> String
const unlines = xs => xs.join('\n');

// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = (f, xs, ys) =>
Array.from({
length: Math.min(xs.length, ys.length)
}, (_, i) => f(xs[i], ys[i], i));

// MAIN ---------------------------------------------------
return main();
})();

``````

Interesting (to me):

The use case looks like restructuring a CSV file. Is it?

Which leads to my thought that some macros to manipulate CSV - such as deleting columns, inverting a CSV etc would be handy.

My personal first choice (for writing such things) would be the Python CSV reader module

(See, for example Trying to parse csv file into variables)

( I first met it when I had to process a 4Gb CSV file in an unexpected hurry â€“ to my surprise it worked perfectly for that, and has always served well since )

Hey Billy,

Give this a try.

-Chris

Tho even AppleScript could probably manage it

``````-- commaJoined :: String -> String -> String
on commaJoined(x, y)
x & "," & y
end commaJoined

on run
tell application "Keyboard Maestro Engine"
set strFirst to getvariable ("firstCol")
set strSecond to getvariable ("secondCol")
end tell

set strTwoCols to Â¬
unlines(zipWith(commaJoined, Â¬
paragraphs of strFirst, Â¬
paragraphs of strSecond))
end run

-- GENERIC REUSABLE FUNCTIONS --------------------------------------

-- min :: Ord a => a -> a -> a
on min(x, y)
if y < x then
y
else
x
end if
end min

-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
if class of f is script then
f
else
script
property |Î»| : f
end script
end if
end mReturn

-- unlines :: [String] -> String
on unlines(xs)
set {dlm, my text item delimiters} to Â¬
{my text item delimiters, linefeed}
set str to xs as text
set my text item delimiters to dlm
str
end unlines

-- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
on zipWith(f, xs, ys)
set lng to min(length of xs, length of ys)
if lng < 1 then return {}
set lst to {}
tell mReturn(f)
repeat with i from 1 to lng
set end of lst to |Î»|(item i of xs, item i of ys)
end repeat
return lst
end tell
end zipWith
``````

Thanks. I have some experience with Pythonâ€™s csv module. Choreographing it with KM would be the novel bit.

Iâ€™m wondering how to pass selected text into a Python module as a string - using KM. I should experiment.

I just need to do the CSV stuff. Next stop is â€śput first column of CSV on clipboard back on clipboardâ€ť - as a simple example.

1 Like

I have a vague recollection (Peter will know) that there can be UTF8 limitations with reading KMVars through os.environ.

The other thing is, of course, to write out to a temporary file, and get Python to read that.

My JSA Action staples for those things include:

``````// filePath :: String -> FilePath
const filePath = s =>
ObjC.unwrap(ObjC.wrap(s)
.stringByStandardizingPath);

// getTemporaryDirectory :: IO FilePath
const getTemporaryDirectory = () =>
ObjC.unwrap(\$.NSTemporaryDirectory());

// takeBaseName :: FilePath -> String
const takeBaseName = strPath =>
strPath !== '' ? (
strPath[strPath.length - 1] !== '/' ? (() => {
const fn = strPath.split('/').slice(-1)[0];
return fn.includes('.') ? (
fn.split('.').slice(0, -1).join('.')
) : fn;
})() : ''
) : '';

// takeExtension :: FilePath -> String
const takeExtension = strPath => {
const
xs = strPath.split('.'),
lng = xs.length;
return lng > 1 ? (
'.' + xs[lng - 1]
) : '';
};

// takeFileName :: FilePath -> FilePath
const takeFileName = strPath =>
strPath !== '' ? (
strPath[strPath.length - 1] !== '/' ? (
strPath.split('/')
.slice(-1)[0]
) : ''
) : '';

// File name template -> temporary path
// (Random digit sequence inserted between template base and extension)
// tempFilePath :: String -> IO FilePath
const tempFilePath = template =>
ObjC.unwrap(\$.NSTemporaryDirectory()) +
takeBaseName(template) + Math.random()
.toString()
.substring(3) + takeExtension(template);

// File name template -> string data -> IO temporary path
// writeTempFile :: String -> String -> IO FilePath
const writeTempFile = (template, txt) => {
const
strPath = ObjC.unwrap(\$.NSTemporaryDirectory()) +
takeBaseName(template) + Math.random()
.toString()
.substring(3) + takeExtension(template);
return (writeFile(strPath, txt), strPath);
};

// writeFile :: FilePath -> String -> IO ()
const writeFile = (strPath, strText) =>
\$.NSString.alloc.initWithUTF8String(strText)
.writeToFileAtomicallyEncodingError(
\$(strPath)
.stringByStandardizingPath, false,
\$.NSUTF8StringEncoding, null
);
``````

So I have a macro - using inline Python - that prompts the user for space-separated column numbers and then selects those columns from CSV data on the clipboard. Today it prints the resulting CSV but I will modify to put the result back on the clipboard.

Itâ€™s pretty crappy Python - and others could do better - but it works well enough. And it uses the built-in Python csv class.

Iâ€™m not sure if itâ€™s worth sharing. Perhaps I will and someone can work on it.

Anyway itâ€™s a nice â€śproof of conceptâ€ť, the concept being that you can manipulate CSV data with a KM macro. Which is actually VERY useful to me.

1 Like

Hey Martin,

That works.

Take careful note of this as well. The Execute a Shell Script action can provide STDIN from a number of sources.

This one uses the Clipboard: